From 28174e194bbcda20051f4c535da34268386a13cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levy=2EXie-=E8=A7=A3=E5=AE=89=E6=A3=AE?= Date: Wed, 25 Dec 2024 09:37:59 +0800 Subject: [PATCH 1/5] =?UTF-8?q?REPORT-145112=20feat:=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=99=A8=E4=BA=A4=E4=BA=92=E5=8A=A8=E7=94=BB-=E9=81=AE?= =?UTF-8?q?=E7=BD=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fine/component/popup/ComponentLayer.java | 228 ++++++++++++++ .../fine/component/popup/GlassPaneChild.java | 47 +++ .../com/fine/component/popup/GlassPopup.java | 131 ++++++++ .../component/popup/GlassPopupManager.java | 292 ++++++++++++++++++ .../com/fine/component/popup/ImageChild.java | 32 ++ .../fine/component/popup/ProgressChild.java | 142 +++++++++ .../fine/component/popup/WindowSnapshots.java | 85 +++++ .../com/fine/theme/utils/FineUIUtils.java | 53 ++++ .../fine/theme/utils/GlassLayerLoader.java | 103 ++++++ .../java/com/fr/design/EnvChangeEntrance.java | 25 +- .../main/java/com/fr/start/ServerStarter.java | 32 +- .../fr/start/server/DesignEmbedHelper.java | 4 - .../start/server/FineEmbedServerMonitor.java | 1 + .../fr/startup/ui/StartupLoadingPanel.java | 1 + .../com/fr/startup/ui/StartupPageWindow.java | 76 ++--- .../com/fine/component/pop/loading.gif | Bin 0 -> 33932 bytes .../components/GlassLoaderStoryBoard.java | 62 ++++ .../gui/storybook/components/loadingBar.gif | Bin 0 -> 609 bytes 18 files changed, 1230 insertions(+), 84 deletions(-) create mode 100755 designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java create mode 100755 designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java create mode 100755 designer-base/src/main/java/com/fine/component/popup/GlassPopup.java create mode 100755 designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java create mode 100644 designer-base/src/main/java/com/fine/component/popup/ImageChild.java create mode 100644 designer-base/src/main/java/com/fine/component/popup/ProgressChild.java create mode 100755 designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java create mode 100644 designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java create mode 100644 designer-base/src/main/resources/com/fine/component/pop/loading.gif create mode 100644 designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java create mode 100644 designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif diff --git a/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java b/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java new file mode 100755 index 0000000000..a69ca0fe0a --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java @@ -0,0 +1,228 @@ +package com.fine.component.popup; + +import com.fine.theme.utils.FineUIUtils; +import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.Animator; +import com.formdev.flatlaf.util.CubicBezierEasing; +import com.formdev.flatlaf.util.UIScale; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JPanel; +import java.awt.AlphaComposite; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.util.Stack; + +/** + * 遮罩功能:渐显遮罩层 + * + * @author Levy.Xie + * @since 12.0 + * Created on 2024/12/12 + */ +public class ComponentLayer extends JPanel { + + private GlassPaneChild component; + private GlassPaneChild nextComponent; + private Animator animator; + private float animate; + private boolean showSnapshot; + private boolean simpleSnapshot; + private Image image; + + private Image nextImage; + private Stack componentStack; + private boolean push; + + private void pushStack(GlassPaneChild component) { + if (componentStack == null) { + componentStack = new Stack<>(); + } + componentStack.push(component); + } + + public ComponentLayer(GlassPaneChild component) { + this.component = component; + init(); + } + + private void init() { + MigLayout layout = new MigLayout("insets 0,fill", "fill", "fill"); + setLayout(layout); + if (component.getRoundBorder() > 0) { + setOpaque(false); + component.setOpaque(false); + } + add(component); + component.setVisible(false); + } + + private void initAnimator() { + animator = new Animator(350, new Animator.TimingTarget() { + + @Override + public void timingEvent(float v) { + animate = v; + repaint(); + } + + @Override + public void begin() { + nextImage = FineUIUtils.createImage(nextComponent); + } + + @Override + public void end() { + showSnapshot = false; + component = nextComponent; + component.popupShow(); + nextComponent = null; + animate = 0; + component.setVisible(true); + if (nextImage != null) { + nextImage.flush(); + } + } + }); + animator.setInterpolator(CubicBezierEasing.EASE); + } + + private void startAnimate() { + if (animator == null) { + initAnimator(); + } + if (animator.isRunning()) { + animator.stop(); + } + animate = 0; + animator.start(); + } + + /** + * 放入组件 + * + * @param component 组件 + */ + public void pushComponent(GlassPaneChild component) { + component.onPush(); + pushStack(this.component); + push = true; + showComponent(component); + } + + + /** + * 弹出组件 + */ + public void popComponent() { + if (!componentStack.isEmpty()) { + GlassPaneChild component = componentStack.pop(); + component.onPop(); + push = false; + showComponent(component); + } + } + + private void showComponent(GlassPaneChild component) { + showSnapshot = true; + this.nextComponent = component; + image = FineUIUtils.createImage(this.component); + if (component.getRoundBorder() > 0) { + setOpaque(false); + component.setOpaque(false); + } + component.setVisible(false); + remove(this.component); + add(component); + revalidate(); + startAnimate(); + } + + /** + * 显示遮罩 + */ + public void showSnapshot() { + if (animator != null && animator.isRunning()) { + animator.stop(); + } + simpleSnapshot = true; + doLayout(); + image = FineUIUtils.createImage(component, component.getRoundBorder()); + component.setVisible(false); + } + + /** + * 隐藏遮罩 + */ + public void hideSnapshot() { + showSnapshot = false; + simpleSnapshot = false; + component.setVisible(true); + if (image != null) { + image.flush(); + } + } + + @Override + protected void paintComponent(Graphics g) { + if (!isOpaque() && component.getRoundBorder() > 0) { + Graphics2D g2 = (Graphics2D) g.create(); + FlatUIUtils.setRenderingHints(g2); + g2.setColor(getBackground()); + int arc = UIScale.scale(component.getRoundBorder()); + FlatUIUtils.paintComponentBackground(g2, 0, 0, getWidth(), getHeight(), 0, arc); + g2.dispose(); + } + super.paintComponent(g); + } + + @Override + public void paint(Graphics g) { + if (simpleSnapshot) { + g.drawImage(image, 0, 0, null); + } else if (showSnapshot) { + int width = getWidth(); + int height = getHeight(); + if (width > 0 && height > 0) { + BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = bufferedImage.createGraphics(); + FlatUIUtils.setRenderingHints(g2); + int arc = UIScale.scale(component.getRoundBorder()); + g2.setColor(getBackground()); + FlatUIUtils.paintComponentBackground(g2, 0, 0, width, height, 0, arc); + if (image != null) { + int w = image.getWidth(null); + double x; + if (push) { + x = -w * animate; + } else { + x = w * animate; + } + g2.setComposite(AlphaComposite.SrcAtop.derive(1f - animate)); + g2.drawImage(image, (int) x, 0, null); + } + if (nextImage != null) { + int w = nextImage.getWidth(null); + double x; + if (push) { + x = getWidth() - (w * animate); + } else { + x = -getWidth() + (w * animate); + } + g2.setComposite(AlphaComposite.SrcAtop.derive(animate)); + g2.drawImage(nextImage, (int) x, 0, null); + } + g2.dispose(); + g.drawImage(bufferedImage, 0, 0, null); + } + } else { + super.paint(g); + } + } + + public GlassPaneChild getComponent() { + return component; + } +} diff --git a/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java b/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java new file mode 100755 index 0000000000..4d248b5cf3 --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java @@ -0,0 +1,47 @@ +package com.fine.component.popup; + +import com.formdev.flatlaf.ui.FlatUIUtils; + +import javax.swing.JPanel; + +/** + * 遮罩加载面板组件 + * + * @author Levy.Xie + * @since 12.0 + * Created on 2024/11/28 + */ +public class GlassPaneChild extends JPanel { + + public int getRoundBorder() { + return FlatUIUtils.getUIInt("Component.arc", 6); + } + + /** + * 放入组件时的回调方法 + */ + public void onPush() { + + } + + /** + * 弹出组件时的回调方法 + */ + public void onPop() { + + } + + /** + * 显示弹窗组件时的回调方法 + */ + public void popupShow() { + } + + /** + * 关闭窗口时的回调方法 + */ + public void onClose() { + + } + +} diff --git a/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java b/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java new file mode 100755 index 0000000000..1159f35475 --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java @@ -0,0 +1,131 @@ +package com.fine.component.popup; + +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.Animator; +import com.formdev.flatlaf.util.CubicBezierEasing; +import net.miginfocom.swing.MigLayout; + +import javax.swing.JComponent; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Component; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.geom.Rectangle2D; + +/** + * 遮罩功能: 弹出面板组件 + * + * @author Levy.Xie + * @since 12.0 + * Created on 2024/12/12 + */ +public class GlassPopup extends JComponent { + private final GlassPopupManager parent; + private final ComponentLayer componentLayer; + private Animator animator; + private MigLayout layout; + private float animate; + private boolean show; + + public GlassPopup(GlassPopupManager parent, GlassPaneChild component) { + this.parent = parent; + this.componentLayer = new ComponentLayer(component); + init(); + initAnimator(); + } + + private void init() { + layout = new MigLayout(); + setLayout(layout); + add(componentLayer, getLayout(parent.layerPane, 0)); + } + + private void initAnimator() { + animator = new Animator(400, new Animator.TimingTarget() { + @Override + public void timingEvent(float fraction) { + if (show) { + animate = fraction; + } else { + animate = 1f - fraction; + } + float f = Math.round(animate * 100) / 100f; + String lc = getLayout(parent.layerPane, f); + layout.setComponentConstraints(componentLayer, lc); + repaint(); + revalidate(); + } + + @Override + public void begin() { + componentLayer.showSnapshot(); + parent.windowSnapshots.createSnapshot(); + parent.contentPane.setVisible(false); + } + + @Override + public void end() { + componentLayer.hideSnapshot(); + if (show) { + componentLayer.getComponent().popupShow(); + } else { + parent.removePopup(GlassPopup.this); + } + parent.contentPane.setVisible(true); + parent.windowSnapshots.removeSnapshot(); + } + }); + animator.setInterpolator(CubicBezierEasing.EASE); + } + + public void setShowPopup(boolean show) { + if (this.show != show) { + // 前置动画未结束时,先关闭前置动画 + if (animator.isRunning()) { + animator.stop(); + } + this.show = show; + animator.start(); + if (show) { + setFocusCycleRoot(true); + } else { + uninstallListener(); + } + } + } + + private void uninstallListener() { + if (getMouseListeners().length != 0) { + removeMouseListener(getMouseListeners()[0]); + } + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + g2.setComposite(AlphaComposite.SrcOver.derive(animate * 0.5f)); + g2.setColor(background()); + g2.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight())); + g2.setComposite(AlphaComposite.SrcOver.derive(animate)); + super.paintComponent(g); + } + + protected Component getComponent() { + return componentLayer.getComponent(); + } + + protected ComponentLayer getComponentLayer() { + return componentLayer; + } + + private Color background() { + return FlatLaf.isLafDark() ? new Color(50, 50, 50) : new Color(20, 20, 20); + } + + private String getLayout(Component parent, float animate) { + float an = 20f / 600; + float space = 0.5f + an - (animate * an); + return "pos 0.5al " + space + "al,height ::100%-20"; + } +} diff --git a/designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java b/designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java new file mode 100755 index 0000000000..c286af31d8 --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/GlassPopupManager.java @@ -0,0 +1,292 @@ +package com.fine.component.popup; + +import javax.swing.JLayeredPane; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; +import java.awt.CardLayout; +import java.awt.Component; +import java.awt.Container; +import java.awt.Window; +import java.awt.event.MouseAdapter; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * 遮罩功能: 遮罩面板管理器 + * + * @author Levy.Xie + * @since 12.0 + * Created on 2024/12/12 + */ +public class GlassPopupManager { + + private static GlassPopupManager instance; + protected WindowSnapshots windowSnapshots; + protected JLayeredPane layerPane; + protected Container contentPane; + + private GlassPopupManager() { + init(); + } + + private void init() { + layerPane = new JLayeredPane(); + layerPane.setLayout(new CardLayout()); + } + + /** + * 添加并显示弹窗组件 + * + * @param component 组件 + * @param name 组件名 + */ + protected void addAndShowPopup(GlassPaneChild component, String name) { + GlassPopup popup = new GlassPopup(this, component); + instance.layerPane.applyComponentOrientation(instance.contentPane.getComponentOrientation()); + popup.applyComponentOrientation(instance.contentPane.getComponentOrientation()); + if (name != null) { + popup.setName(name); + } + layerPane.setLayer(popup, JLayeredPane.DRAG_LAYER); + layerPane.add(popup, 0); + popup.setVisible(true); + popup.setShowPopup(true); + if (!layerPane.isVisible()) { + layerPane.setVisible(true); + } + layerPane.grabFocus(); + } + + private void updateLayout() { + for (Component com : layerPane.getComponents()) { + com.revalidate(); + } + } + + /** + * 设置遮罩父窗口 + * + * @param container 遮罩父容器 + */ + public static void install(Container container) { + instance = new GlassPopupManager(); + Window window; + if (container instanceof Window) { + window = (Window) container; + } else { + window = SwingUtilities.getWindowAncestor(container); + } + instance.windowSnapshots = new WindowSnapshots(window); + + window.addWindowStateListener(new WindowAdapter() { + @Override + public void windowStateChanged(WindowEvent e) { + SwingUtilities.invokeLater(() -> { + instance.updateLayout(); + }); + } + }); + + JRootPane rootPane = SwingUtilities.getRootPane(container); + instance.contentPane = rootPane.getContentPane(); + rootPane.setGlassPane(instance.layerPane); + } + + /** + * 显示组件 + * + * @param component 组件 + * @param name 组件名 + */ + public static void showPopup(GlassPaneChild component, String name) { + if (component.getMouseListeners().length == 0) { + component.addMouseListener(new MouseAdapter() { + }); + } + instance.addAndShowPopup(component, name); + } + + /** + * 显示组件 + * + * @param component 组件 + */ + public static void showPopup(GlassPaneChild component) { + showPopup(component, null); + } + + /** + * 压栈,放入弹窗组件 + * + * @param component 组件 + * @param name 组件名 + */ + public static void push(GlassPaneChild component, String name) { + for (Component com : instance.layerPane.getComponents()) { + if (com.getName() != null && com.getName().equals(name)) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + popup.getComponentLayer().pushComponent(component); + break; + } + } + } + } + + /** + * 出栈,弹出弹窗组件 + * + * @param name 组件名 + */ + public static void pop(String name) { + for (Component com : instance.layerPane.getComponents()) { + if (com.getName() != null && com.getName().equals(name)) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + popup.getComponentLayer().popComponent(); + break; + } + } + } + } + + /** + * 出栈,弹出弹窗组件 + * + * @param component 组件 + */ + public static void pop(Component component) { + for (Component com : instance.layerPane.getComponents()) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + if (popup.getComponent() == component) { + popup.getComponentLayer().popComponent(); + } + } + } + } + + /** + * 基于序号关闭弹窗组件 + * + * @param index 序号 + */ + public static void closePopup(int index) { + index = instance.layerPane.getComponentCount() - 1 - index; + if (index >= 0 && index < instance.layerPane.getComponentCount()) { + if (instance.layerPane.getComponent(index) instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) instance.layerPane.getComponent(index); + popup.setShowPopup(false); + } + } + } + + /** + * 基于名称关闭组件 + * + * @param name 组件名称 + */ + public static void closePopup(String name) { + for (Component com : instance.layerPane.getComponents()) { + if (com.getName() != null && com.getName().equals(name)) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + popup.setShowPopup(false); + } + } + } + } + + /** + * 关闭组件 + * + * @param component 组件 + */ + public static void closePopup(Component component) { + for (Component com : instance.layerPane.getComponents()) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + if (popup.getComponent() == component) { + popup.setShowPopup(false); + } + } + } + } + + /** + * 关闭最后一个组件 + */ + public static void closePopupLast() { + closePopup(getPopupCount() - 1); + } + + /** + * 关闭全部组件 + */ + public static void closePopupAll() { + for (Component com : instance.layerPane.getComponents()) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + popup.setShowPopup(false); + } + } + } + + /** + * 基于组件名,判断组件是否显示 + * + * @param name 组件名 + * @return 是否显示 + */ + public static boolean isShowing(String name) { + boolean act = false; + for (Component com : instance.layerPane.getComponents()) { + if (com.getName() != null && com.getName().equals(name)) { + act = true; + break; + } + } + return act; + } + + /** + * 判断组件是否显示 + * + * @param component 组件 + * @return 是否显示 + */ + public static boolean isShowing(Component component) { + boolean act = false; + for (Component com : instance.layerPane.getComponents()) { + if (com instanceof GlassPopup) { + GlassPopup popup = (GlassPopup) com; + if (popup.getComponent() == component) { + act = true; + break; + } + } + } + return act; + } + + /** + * 获取弹窗组件数量 + * + * @return 弹窗数量 + */ + public static int getPopupCount() { + return instance.layerPane.getComponentCount(); + } + + /** + * 移除组件 + * + * @param popup 弹窗组件 + */ + protected synchronized void removePopup(Component popup) { + layerPane.remove(popup); + if (layerPane.getComponentCount() == 0) { + layerPane.setVisible(false); + } + } +} diff --git a/designer-base/src/main/java/com/fine/component/popup/ImageChild.java b/designer-base/src/main/java/com/fine/component/popup/ImageChild.java new file mode 100644 index 0000000000..5fd01ac63d --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/ImageChild.java @@ -0,0 +1,32 @@ +package com.fine.component.popup; + +import com.formdev.flatlaf.util.ScaledEmptyBorder; +import com.fr.design.gui.ilable.UILabel; + +import javax.swing.ImageIcon; +import java.awt.BorderLayout; +import java.util.Objects; + +/** + * 图片遮罩层,支撑静态图片及动态GIF + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/12/20 + */ +public class ImageChild extends GlassPaneChild { + + private static final String DEFAULT_LOADING_PATH = "com/fine/component/pop/loading.gif"; + + public ImageChild() { + this(DEFAULT_LOADING_PATH); + } + + public ImageChild(String imgPath) { + setLayout(new BorderLayout()); + setBorder(new ScaledEmptyBorder(8, 8, 8, 8)); + ImageIcon icon = new ImageIcon(Objects.requireNonNull(getClass().getClassLoader().getResource(imgPath))); + add(new UILabel(icon), BorderLayout.CENTER); + } + +} diff --git a/designer-base/src/main/java/com/fine/component/popup/ProgressChild.java b/designer-base/src/main/java/com/fine/component/popup/ProgressChild.java new file mode 100644 index 0000000000..d56cafc95a --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/ProgressChild.java @@ -0,0 +1,142 @@ +package com.fine.component.popup; + +import com.fine.theme.utils.FineUIScale; +import com.formdev.flatlaf.util.ScaledEmptyBorder; +import com.fr.concurrent.NamedThreadFactory; +import com.fr.design.locale.impl.SupportLocaleImpl; +import com.fr.design.ui.util.UIUtil; +import com.fr.design.utils.DesignUtils; +import com.fr.general.FRFont; +import com.fr.general.locale.LocaleCenter; + +import javax.swing.JLabel; +import javax.swing.JProgressBar; +import javax.swing.plaf.ColorUIResource; + +import java.awt.BorderLayout; +import java.awt.Font; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static com.fine.swing.ui.layout.Layouts.cell; +import static com.fine.swing.ui.layout.Layouts.column; +import static com.fine.swing.ui.layout.Layouts.flex; +import static com.fine.swing.ui.layout.Layouts.row; +import static com.fine.theme.utils.FineUIScale.scale; + +/** + * 进度条遮罩组件 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/12/13 + */ +public class ProgressChild extends GlassPaneChild { + + private int progress; + protected JLabel text; + protected JProgressBar progressBar; + + private int step = 10; + private static final int STEP_HEARTBEAT = 40; + + private static final int FONT_RGB = 333334; + private static final int FONT_SIZE = 14; + + private static final String FONT_NAME = "Dialog"; + + public ProgressChild(String text) { + this(text, DesignUtils.getDefaultGUIFont() + .applySize(scale(FONT_SIZE)) + .applyForeground(new ColorUIResource(FONT_RGB))); + } + + public ProgressChild(String text, Font font) { + initComponents(text, font); + initLayout(); + initScheduler(); + } + + private void initComponents(String text, Font font) { + this.text = new JLabel(text); + this.text.setFont(font); + //由于默认值的字体不支持韩文,所以要对韩文单独生成字体 + LocaleCenter.buildAction(() -> { + FRFont frFont = FRFont.getInstance().applySize(FONT_SIZE) + .applyForeground(new ColorUIResource(FONT_RGB)).applyName(FONT_NAME); + this.text.setFont(frFont); + }, SupportLocaleImpl.SUPPORT_KOREA); + this.progressBar = new JProgressBar(); + progressBar.setBorderPainted(false); + progressBar.setOpaque(false); + progressBar.setBorder(null); + progressBar.setMaximum(1000); + } + + /** + * 设置进度条最大加载时间,单位为s + * + * @param maxWait 最大等待时间 + * @return 进度条遮罩层 + */ + public ProgressChild setMaxWait(int maxWait) { + this.step = progressBar.getMaximum() * STEP_HEARTBEAT / (maxWait * 1000); + return this; + } + + private void initLayout() { + setLayout(new BorderLayout()); + setPreferredSize(FineUIScale.createScaleDimension(400, 100)); + add(column(10, + cell(progressBar).weight(1), row(flex(), cell(text), flex()).weight(1.5) + ).with(it -> it.setBorder(new ScaledEmptyBorder(30, 30, 20, 30))).getComponent(), + BorderLayout.CENTER); + } + + private void initScheduler() { + final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, + new NamedThreadFactory(getClass().getSimpleName())); + scheduler.scheduleWithFixedDelay(() -> { + if (isComplete() && !scheduler.isShutdown()) { + scheduler.shutdown(); + return; + } + UIUtil.invokeLaterIfNeeded(() -> progressBar.setValue(incrementProgress())); + }, 0, STEP_HEARTBEAT, TimeUnit.MILLISECONDS); + } + + /** + * 递增进度条 + * + * @return 进度值 + */ + public int incrementProgress() { + if (progress != progressBar.getMaximum()) { + progress += step; + } + return progress; + } + + @Override + public void onClose() { + this.progress = progressBar.getMaximum(); + } + + /** + * 重置进度条 + */ + public void reset() { + this.progress = 0; + } + + /** + * 进度是否已结束 + * + * @return 进度结束状态 + */ + public boolean isComplete() { + return this.progress >= progressBar.getMaximum(); + } + +} diff --git a/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java b/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java new file mode 100755 index 0000000000..6ecfe471dd --- /dev/null +++ b/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java @@ -0,0 +1,85 @@ +package com.fine.component.popup; + +import javax.swing.JComponent; +import javax.swing.JLayeredPane; +import javax.swing.JRootPane; +import javax.swing.SwingUtilities; +import java.awt.Graphics; +import java.awt.Window; +import java.awt.image.VolatileImage; + +/** + * WindowSnapshots, 捕获 Window 的快照 + * + * @author Levy.Xie + * @since 12.0 + * Created on 2024/12/12 + */ +public class WindowSnapshots { + + private final Window window; + private JComponent snapshotLayer; + private boolean inShowSnapshot; + + public WindowSnapshots(Window window) { + this.window = window; + } + + /** + * 创建窗口快照并将其显示为覆盖层 + */ + public void createSnapshot() { + if (inShowSnapshot) { + return; + } + inShowSnapshot = true; + if ((window != null && window.isShowing())) { + VolatileImage snapshot = window.createVolatileImage(window.getWidth(), window.getHeight()); + if (snapshot != null) { + JLayeredPane layeredPane = getLayeredPane(); + if (layeredPane != null) { + layeredPane.paint(snapshot.getGraphics()); + snapshotLayer = new JComponent() { + @Override + public void paint(Graphics g) { + if (snapshot.contentsLost()) { + return; + } + g.drawImage(snapshot, 0, 0, null); + } + + @Override + public void removeNotify() { + super.removeNotify(); + snapshot.flush(); + } + }; + snapshotLayer.setSize(layeredPane.getSize()); + layeredPane.add(snapshotLayer, Integer.valueOf(JLayeredPane.DRAG_LAYER + 1)); + } + } + } + } + + /** + * 移除窗口快照覆盖层 + */ + public void removeSnapshot() { + if (snapshotLayer == null) { + return; + } + JLayeredPane layeredPane = getLayeredPane(); + if (layeredPane != null) { + layeredPane.remove(snapshotLayer); + } + inShowSnapshot = false; + } + + private JLayeredPane getLayeredPane() { + JRootPane rootPane = SwingUtilities.getRootPane(window); + if (rootPane != null) { + return rootPane.getLayeredPane(); + } + return null; + } +} diff --git a/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java b/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java index 8065cf6d33..dc438ad4e0 100644 --- a/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java +++ b/designer-base/src/main/java/com/fine/theme/utils/FineUIUtils.java @@ -2,6 +2,7 @@ package com.fine.theme.utils; import com.fine.theme.light.ui.CollapsibleScrollBarLayerUI; import com.formdev.flatlaf.ui.FlatUIUtils; +import com.formdev.flatlaf.util.UIScale; import com.fr.design.border.FineBorderFactory; import com.fr.design.constants.LayoutConstants; import com.fr.design.gui.icontainer.UIScrollPane; @@ -12,12 +13,14 @@ import com.fr.design.mainframe.theme.edit.ui.LabelUtils; import com.fr.design.i18n.DesignSizeI18nManager; import com.fr.stable.os.OperatingSystem; import com.fr.value.AtomicClearableLazyValue; +import org.jetbrains.annotations.Nullable; import javax.swing.JLabel; import javax.swing.JLayer; import javax.swing.ScrollPaneConstants; import javax.swing.UIManager; import javax.swing.JTextArea; +import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Component; import java.awt.Composite; @@ -27,9 +30,13 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; +import java.awt.Image; import java.awt.Insets; +import java.awt.RenderingHints; import java.awt.geom.Path2D; import java.awt.geom.RoundRectangle2D; +import java.awt.image.BufferedImage; +import java.awt.image.VolatileImage; import java.lang.reflect.Field; import static com.fine.swing.ui.layout.Layouts.cell; @@ -517,4 +524,50 @@ public class FineUIUtils { DesignerFrame parent = DesignerContext.getDesignerFrame(); return new Dimension((int) (parent.getWidth() * width),(int) (parent.getHeight() * height)); } + + /** + * 创建一个与指定组件大小相同的 VolatileImage,并将组件内容绘制到该图像上。 + * + * @param component 组件 + * @return 包含组件内容的 VolatileImage;如果组件的宽度或高度为零,则返回 null + */ + @Nullable + public static VolatileImage createImage(Component component) { + int width = component.getWidth(); + int height = component.getHeight(); + if (width > 0 && height > 0) { + VolatileImage image = component.createVolatileImage(width, height); + component.paint(image.createGraphics()); + return image; + } + return null; + } + + /** + * 创建一个与指定组件内容相同并带有圆角的 BufferedImage。 + * + * @param component 要生成图像的组件 + * @param round 圆角半径 + * @return 包含圆角组件内容的 BufferedImage;如果组件的宽度或高度为零,则返回 null + */ + public static Image createImage(Component component, int round) { + int width = component.getWidth(); + int height = component.getHeight(); + if (width > 0 && height > 0) { + VolatileImage image = createImage(component); + if (image != null) { + component.paint(image.createGraphics()); + BufferedImage buffImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = buffImage.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + int arc = UIScale.scale(round); + FlatUIUtils.paintComponentBackground(g, 0, 0, width, height, 0, arc); + g.setComposite(AlphaComposite.SrcIn); + g.drawImage(image, 0, 0, null); + image.flush(); + return buffImage; + } + } + return null; + } } diff --git a/designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java b/designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java new file mode 100644 index 0000000000..ced8ba30df --- /dev/null +++ b/designer-base/src/main/java/com/fine/theme/utils/GlassLayerLoader.java @@ -0,0 +1,103 @@ +package com.fine.theme.utils; + +import com.fine.component.popup.ProgressChild; +import com.fine.component.popup.GlassPopupManager; +import com.fine.component.popup.GlassPaneChild; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.ui.util.UIUtil; +import com.fr.log.FineLoggerFactory; + +import javax.swing.SwingWorker; +import java.awt.Container; +import java.util.concurrent.Callable; +import java.util.function.Consumer; + +/** + * 遮罩层加载器 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/12/06 + */ +public class GlassLayerLoader { + + private static volatile GlassLayerLoader loader = null; + + private GlassLayerLoader() { + } + + public static GlassLayerLoader getInstance() { + if (loader == null) { + synchronized (GlassLayerLoader.class) { + if (loader == null) { + loader = new GlassLayerLoader(); + } + } + } + return loader; + } + + /** + * 带有加载面板的后台任务执行器(无回调) + * + * @param task 耗时任务 + * @param container 遮罩的底层容器 + * @param loadingPane 显示加载动画的 GlassPaneChild + * @param 任务的返回值类型 + */ + public void runWithLoader(Callable task, Container container, GlassPaneChild loadingPane) { + // 默认无回调 + runWithLoader(task, container, loadingPane, result -> {}, e -> {}); + } + + /** + * 带有加载面板的后台任务执行器(有成功和失败的回调) + * + * @param task 耗时任务 + * @param container 遮罩的底层容器 + * @param loadingPane 显示加载动画的 GlassPaneChild + * @param 任务的返回值类型 + */ + public void runWithLoader(Callable task, Container container, GlassPaneChild loadingPane, + Consumer onSuccess, Consumer onFailure) { + // 安装并显示加载面板 + UIUtil.invokeAndWaitIfNeeded(() -> { + GlassPopupManager.install(container); + GlassPopupManager.showPopup(loadingPane); + }); + SwingWorker worker = new SwingWorker() { + @Override + protected T doInBackground() throws Exception { + return task.call(); + } + + @Override + protected void done() { + try { + T t = get(); + onSuccess.accept(t); + } catch (Exception e) { + onFailure.accept(e); + FineLoggerFactory.getLogger().error(e.getMessage()); + } finally { + loadingPane.onClose(); + GlassPopupManager.closePopup(loadingPane); + } + } + }; + worker.execute(); + } + + /** + * 进度条遮罩加载动画 + * + * @param task 耗时任务 + * @param info 显示加载面板的 JFrame + * @param maxWait 最大等待时间 + * @param 任务的返回值类型 + */ + public void runWithProgressLoader(Callable task, String info, int maxWait) { + runWithLoader(task, DesignerContext.getDesignerFrame(), new ProgressChild(info).setMaxWait(maxWait)); + } + +} diff --git a/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java b/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java index 4b3eec482f..4b8c41659c 100644 --- a/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java +++ b/designer-base/src/main/java/com/fr/design/EnvChangeEntrance.java @@ -1,5 +1,6 @@ package com.fr.design; +import com.fine.theme.utils.GlassLayerLoader; import com.fr.common.report.ReportState; import com.fr.design.backup.EnvBackupHelper; import com.fr.design.data.DesignTableDataManager; @@ -102,9 +103,12 @@ public class EnvChangeEntrance { * @param envName 目标工作目录名称 */ public void switch2Env(final String envName) { - if (switch2Env(envName, PopTipStrategy.LATER)) { - VersionCheckUtils.showVersionCheckDialog(envName); - } + GlassLayerLoader.getInstance().runWithProgressLoader(() -> { + if (switch2Env(envName, PopTipStrategy.LATER)) { + VersionCheckUtils.showVersionCheckDialog(envName); + } + return null; + }, Toolkit.i18nText("Fine-Design_Basic_M_Switch_Workspace"), 5); } /** @@ -525,12 +529,15 @@ public class EnvChangeEntrance { // 用户取消保存时,取消切换目录操作 return; } - boolean changeResult = envListOkAction(envListPane, PopTipStrategy.LATER); - // 切换完成后清理密码 - updateNotRememberPwdEnv(); - if (changeResult) { - VersionCheckUtils.showVersionCheckDialog(envListPane.getSelectedName()); - } + GlassLayerLoader.getInstance().runWithProgressLoader(() -> { + boolean changeResult = envListOkAction(envListPane, PopTipStrategy.LATER); + // 切换完成后清理密码 + updateNotRememberPwdEnv(); + if (changeResult) { + VersionCheckUtils.showVersionCheckDialog(envListPane.getSelectedName()); + } + return null; + }, Toolkit.i18nText("Fine-Design_Basic_M_Switch_Workspace"), 5); } @Override diff --git a/designer-base/src/main/java/com/fr/start/ServerStarter.java b/designer-base/src/main/java/com/fr/start/ServerStarter.java index 9c2bfa2620..3d2d748122 100644 --- a/designer-base/src/main/java/com/fr/start/ServerStarter.java +++ b/designer-base/src/main/java/com/fr/start/ServerStarter.java @@ -1,17 +1,15 @@ package com.fr.start; +import com.fine.theme.utils.GlassLayerLoader; import com.fr.base.ServerConfig; -import com.fr.concurrent.NamedThreadFactory; import com.fr.design.DesignerEnvManager; +import com.fr.design.i18n.Toolkit; import com.fr.design.mainframe.DesignerContext; import com.fr.design.utils.BrowseUtils; import com.fr.general.GeneralContext; import com.fr.start.server.FineEmbedServer; -import com.fr.start.server.FineEmbedServerMonitor; import com.fr.workspace.WorkContext; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; public class ServerStarter { @@ -43,25 +41,15 @@ public class ServerStarter { public static void browserURLWithLocalEnv(final String url) { // 内置服务器没有启动并且设计器已经打开,可以使用带进度条的启动方式 if (!FineEmbedServer.isRunning() && DesignerContext.getDesignerFrame().isDesignerOpened()) { - FineEmbedServerMonitor.getInstance().monitor(); - ExecutorService service = Executors.newSingleThreadExecutor(new NamedThreadFactory("ServerStarter")); - service.submit(new Runnable() { - - @Override - public void run() { - try { - try { - FineEmbedServer.start(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } finally { - FineEmbedServerMonitor.getInstance().setComplete(); - } - BrowseUtils.browser(url); + GlassLayerLoader.getInstance().runWithProgressLoader(() -> { + try { + FineEmbedServer.start(); + } catch (Exception e) { + throw new RuntimeException(e); } - }); - service.shutdown(); + BrowseUtils.browser(url); + return null; + }, Toolkit.i18nText("Fine-Design_Basic_Loading_Embed_Server"), 10); } else if (!FineEmbedServer.isRunning()) { // 普通方式启动内置服务器 try { 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 1a1ed91398..057d312143 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 @@ -48,15 +48,11 @@ public class DesignEmbedHelper { public static synchronized void start() { try { - FineEmbedServerMonitor.getInstance().reset(); //初始化tomcat initTomcat(); tomcat.start(); - } catch (LifecycleException e) { FineLoggerFactory.getLogger().error(e.getMessage(), e); - } finally { - FineEmbedServerMonitor.getInstance().setComplete(); } } diff --git a/designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java b/designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java index c20aaa153d..e53cd75f2f 100644 --- a/designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java +++ b/designer-base/src/main/java/com/fr/start/server/FineEmbedServerMonitor.java @@ -24,6 +24,7 @@ import java.util.concurrent.TimeUnit; * @author zack * @date 2018/8/21 */ +@Deprecated public class FineEmbedServerMonitor { private int progress; private static final int STEP = 1; diff --git a/designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java b/designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java index 3faf1c2232..0bdf5bf2cd 100644 --- a/designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java +++ b/designer-base/src/main/java/com/fr/startup/ui/StartupLoadingPanel.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; * @version 11.0 * created by Harrison on 2022/11/08 **/ +@Deprecated public class StartupLoadingPanel { /** diff --git a/designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java b/designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java index 3100c064ae..1e3a02c83a 100644 --- a/designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java +++ b/designer-base/src/main/java/com/fr/startup/ui/StartupPageWindow.java @@ -1,8 +1,10 @@ package com.fr.startup.ui; +import com.fine.component.popup.ProgressChild; import com.fine.swing.ui.layout.Column; import com.fine.theme.utils.FineUIStyle; import com.fine.theme.utils.FineUIUtils; +import com.fine.theme.utils.GlassLayerLoader; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.ScaledEmptyBorder; import com.fr.design.DesignerEnvManager; @@ -15,7 +17,6 @@ import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.layout.VerticalFlowLayout; import com.fr.design.mainframe.messagecollect.StartErrorMessageCollector; import com.fr.design.mainframe.messagecollect.entity.DesignerErrorMessage; -import com.fr.design.ui.util.UIUtil; import com.fr.design.utils.ColorUtils; import com.fr.design.utils.gui.GUICoreUtils; import com.fr.exit.DesignerExiter; @@ -35,7 +36,6 @@ import javax.swing.JPanel; import javax.swing.JSeparator; import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; -import javax.swing.SwingWorker; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; @@ -233,54 +233,32 @@ public class StartupPageWindow extends JFrame { } private void enterWorkspace(Runnable action) { - - UIUtil.invokeAndWaitIfNeeded(() -> { - - // 必须直接初始化 - // 见 https://work.fineres.com/browse/REPORT-85293 - StartupLoadingPanel loadingPanel = new StartupLoadingPanel(this); - loadingPanel.show(); + GlassLayerLoader.getInstance().runWithLoader(() -> { setEnabled(false); - - SwingWorker task = new SwingWorker() { - @Override - protected Void doInBackground() throws Exception { - action.run(); - return null; - } - - @Override - protected void done() { - - try { - Void result = get(); - setVisible(false); - StartErrorMessageCollector.getInstance().setExtraJudgeStart(false); - } catch (Exception e) { - // 处理错误 - UIUtil.invokeLaterIfNeeded(() -> { - UIExpandDialog.Builder() - .owner(StartupPageWindow.this) - .title(Toolkit.i18nText("Fine-Design_Basic_Remote_Env_Try")) - .message(Toolkit.i18nText("Fine-Design_Basic_Connection_Failed")) - .messageType(UIExpandDialog.WARNING_MESSAGE) - .detail(e.getMessage()) - .expand(true) - .modal(false) - .build() - .setVisible(true); - setEnabled(true); - }); - FineLoggerFactory.getLogger().error(e.getMessage(), e); - StartErrorMessageCollector.getInstance().asyncRecord(DesignerErrorMessage.UNEXCEPTED_START_FAILED.getId(), - DesignerErrorMessage.UNEXCEPTED_START_FAILED.getMessage(), - e.getMessage()); - } finally { - loadingPanel.hide(); - } - } - }; - task.execute(); + action.run(); + return null; + }, this, new ProgressChild(Toolkit.i18nText("Fine-Design_Basic_Initializing")).setMaxWait(10), + // 进入工作目录成功回调 + res -> { + setVisible(false); + StartErrorMessageCollector.getInstance().setExtraJudgeStart(false); + }, e -> { + // 进入工作目录失败回调 + UIExpandDialog.Builder() + .owner(StartupPageWindow.this) + .title(Toolkit.i18nText("Fine-Design_Basic_Remote_Env_Try")) + .message(Toolkit.i18nText("Fine-Design_Basic_Connection_Failed")) + .messageType(UIExpandDialog.WARNING_MESSAGE) + .detail(e.getMessage()) + .expand(true) + .modal(false) + .build() + .setVisible(true); + setEnabled(true); + FineLoggerFactory.getLogger().error(e.getMessage(), e); + StartErrorMessageCollector.getInstance().asyncRecord(DesignerErrorMessage.UNEXCEPTED_START_FAILED.getId(), + DesignerErrorMessage.UNEXCEPTED_START_FAILED.getMessage(), + e.getMessage()); }); } diff --git a/designer-base/src/main/resources/com/fine/component/pop/loading.gif b/designer-base/src/main/resources/com/fine/component/pop/loading.gif new file mode 100644 index 0000000000000000000000000000000000000000..2425a6790e72b442db7ee3de7a228f808625e2fa GIT binary patch literal 33932 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 literal 0 HcmV?d00001 diff --git a/designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java b/designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java new file mode 100644 index 0000000000..e5b5ed6cf8 --- /dev/null +++ b/designer-base/src/test/java/com/fr/design/gui/storybook/components/GlassLoaderStoryBoard.java @@ -0,0 +1,62 @@ +package com.fr.design.gui.storybook.components; + +import com.fine.component.popup.ImageChild; +import com.fine.component.popup.ProgressChild; +import com.fine.theme.utils.GlassLayerLoader; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.storybook.StoryBoard; +import com.fr.stable.StringUtils; + +import javax.swing.JFrame; +import javax.swing.SwingUtilities; + +import java.awt.Font; + +import static com.fine.swing.ui.layout.Layouts.cell; + +/** + * GlassLoaderStoryBoard + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/12/19 + */ +public class GlassLoaderStoryBoard extends StoryBoard { + + public GlassLoaderStoryBoard() { + super("遮罩动画"); + add( + cell(createProgressBtn()), + cell(createImageBtn("GIF图片遮罩-1", null)), + cell(createImageBtn("GIF图片遮罩-2", "com/fr/design/gui/storybook/components/loadingBar.gif")) + ); + } + + private UIButton createProgressBtn() { + UIButton btn = new UIButton("进度条遮罩"); + btn.addActionListener(e -> { + GlassLayerLoader.getInstance().runWithLoader(() -> { + Thread.sleep(3000); + return null; + }, (JFrame) SwingUtilities.getWindowAncestor(this), new ProgressChild("running...", + new Font("宋体", Font.PLAIN, 14)).setMaxWait(3)); + }); + return btn; + } + + private UIButton createImageBtn(String name, String path) { + UIButton btn = new UIButton(name); + btn.addActionListener(e -> { + showGlassLoadingPane(path); + }); + return btn; + } + + private void showGlassLoadingPane(String gifPath) { + GlassLayerLoader.getInstance().runWithLoader(() -> { + Thread.sleep(3000); + return null; + }, (JFrame) SwingUtilities.getWindowAncestor(this), StringUtils.isEmpty(gifPath) ? new ImageChild() : new ImageChild(gifPath)); + } + +} diff --git a/designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif b/designer-base/src/test/resources/com/fr/design/gui/storybook/components/loadingBar.gif new file mode 100644 index 0000000000000000000000000000000000000000..1c9a58cea1406734269b36645849e8a3d7c2816d GIT binary patch literal 609 zcmZ?wbhEHbY+&GLn8*MEn>TO%|NlRbq4=NM&ow02*)hP?NY8+o5hV1Jg@p@9>i{tW zNDBkg;vV@$P22BW>I%A8o_-|S{9h%9NAIFyDV40!+#jddRmX=!$g zCYbZA>pHkZm~*S`(!e`cb{!7derw&jUr*ci?tU70W>4Ss%mV3Hqto@p$+4d2Z`Z#v z|M{o~4;h{*|ZS@-Nmwc#0e7DV&mG_#_qaNrD0k zr?KYvB%Sa{`r?y}z$clEPcn~?q!2jxG=S00^tRz#(t20p)q5lFZ^;l4uUev17rS)Y V8_&HLe{ES|x!ZKkrzl1SYXD?dyjK7K literal 0 HcmV?d00001 From 4bba3f18acb5a270f6a844bae7c6bf15fbff2b36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levy=2EXie-=E8=A7=A3=E5=AE=89=E6=A3=AE?= Date: Wed, 25 Dec 2024 09:38:17 +0800 Subject: [PATCH 2/5] =?UTF-8?q?REPORT-145112=20feat:=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=99=A8=E4=BA=A4=E4=BA=92=E5=8A=A8=E7=94=BB-=E6=8A=98?= =?UTF-8?q?=E5=8F=A0=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../design/foldablepane/UIExpandablePane.java | 84 +++++++++++++------ 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java b/designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java index faae8d9f84..355c95ab6d 100644 --- a/designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java +++ b/designer-base/src/main/java/com/fr/design/foldablepane/UIExpandablePane.java @@ -1,12 +1,12 @@ package com.fr.design.foldablepane; - +import com.formdev.flatlaf.util.Animator; import com.formdev.flatlaf.util.ScaledEmptyBorder; import com.fr.design.border.FineBorderFactory; import javax.swing.JPanel; import java.awt.BorderLayout; -import java.awt.Color; +import java.awt.Dimension; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -14,29 +14,29 @@ import static com.fine.swing.ui.layout.Layouts.cell; import static com.fine.swing.ui.layout.Layouts.column; import static com.fine.swing.ui.layout.Layouts.fix; - /** - * Created by MoMeak on 2017/7/5. + * 折叠面板 + * + * @author Levy.Xie + * @since 11.0 + * Created on 2024/11/15 */ public class UIExpandablePane extends JPanel { private static final long serialVersionUID = 1L; - private HeaderPane headerPanel; - private JPanel contentPanel; - private Color color = Color.black; - private String title; - private int headWidth; - private int headHeight; + private boolean showExpand = true; + private Animator animator; + private int fullHeight; + private final JPanel contentPanel; + private final String title; public JPanel getContentPanel() { return contentPanel; } - public UIExpandablePane(String title, int headWidth, int headHeight, JPanel contentPanel) { + public UIExpandablePane(String title, @Deprecated int headWidth, @Deprecated int headHeight, JPanel contentPanel) { super(); this.title = title; - this.headWidth = headWidth; - this.headHeight = headHeight; this.contentPanel = contentPanel; initComponents(false); } @@ -48,48 +48,80 @@ public class UIExpandablePane extends JPanel { public UIExpandablePane(String title, JPanel contentPanel, boolean withUnderline) { super(); this.title = title; - this.headHeight = headHeight; this.contentPanel = contentPanel; initComponents(withUnderline); } private void initComponents(boolean withUnderline) { this.setLayout(new BorderLayout()); - - headerPanel = new HeaderPane(title); + HeaderPane headerPanel = new HeaderPane(title); headerPanel.addMouseListener(new PanelAction()); setcontentPanelontentPanelBorder(); if (withUnderline) { this.add(column( - cell(headerPanel), cell(contentPanel).with(it -> it.setBorder(new ScaledEmptyBorder(0, 0, 10, 0))), + cell(headerPanel), + cell(contentPanel).with(it -> it.setBorder(new ScaledEmptyBorder(0, 0, 10, 0))), fix(1).with(it -> it.setBorder(FineBorderFactory.createDefaultUnderlineBorder()))) .getComponent()); } else { this.add(headerPanel, BorderLayout.NORTH); this.add(contentPanel, BorderLayout.CENTER); } - + initAnimation(); setOpaque(false); } + /** + * 组件竖向折叠显示动画 + */ + private void initAnimation() { + int width = contentPanel.getWidth(); + boolean sizeSet = contentPanel.isPreferredSizeSet(); + animator = new Animator(200, new Animator.TimingTarget() { + @Override + public void timingEvent(float fraction) { + float ratio = !showExpand ? fraction : 1f - fraction; + contentPanel.setPreferredSize(new Dimension(width, (int) (fullHeight * ratio))); + contentPanel.revalidate(); + contentPanel.repaint(); + } + + @Override + public void begin() { + contentPanel.setVisible(true); + if (showExpand) { + fullHeight = contentPanel.getPreferredSize().height; + } + } + + @Override + public void end() { + // 重置中心面板 + if (!sizeSet) { + contentPanel.setPreferredSize(null); + } + contentPanel.setVisible(!showExpand); + showExpand = !showExpand; + } + }); + } + protected void setcontentPanelontentPanelBorder() { } class PanelAction extends MouseAdapter { + @Override public void mouseClicked(MouseEvent e) { HeaderPane hp = (HeaderPane) e.getSource(); - if (contentPanel.isShowing()) { - contentPanel.setVisible(false); - hp.setShow(false); - } else { - contentPanel.setVisible(true); - hp.setShow(true); + if (!Animator.useAnimation()) { + contentPanel.setVisible(!showExpand); + } else if (!animator.isRunning()) { + animator.start(); } + hp.setShow(!showExpand); hp.setPressed(false); - hp.getParent().validate(); - hp.getParent().repaint(); } public void mousePressed(MouseEvent e) { From 5dc43dda6ceb08e89e3222311b41604860acc357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levy=2EXie-=E8=A7=A3=E5=AE=89=E6=A3=AE?= Date: Wed, 25 Dec 2024 09:38:43 +0800 Subject: [PATCH 3/5] =?UTF-8?q?REPORT-145112=20feat:=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=99=A8=E4=BA=A4=E4=BA=92=E5=8A=A8=E7=94=BB-=E5=BC=B9?= =?UTF-8?q?=E5=87=BA=E6=B6=88=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fine/component/toast/ToastMsgHolder.java | 76 +++ .../fine/component/toast/ToastMsgManager.java | 566 ++++++++++++++++++ .../com/fine/component/toast/ToastPane.java | 95 +++ .../com/fine/theme/light/ui/ShadowBorder.java | 122 ++++ .../com/fine/theme/light/ui/ToastPanelUI.java | 374 ++++++++++++ .../theme/utils/FineClientProperties.java | 6 + .../com/fine/theme/utils/ShadowRenderer.java | 193 ++++++ .../DefaultTemplateTreeDefineProcessor.java | 7 +- .../mainframe/ComponentReuseNotifyUtil.java | 4 +- .../theme/TemplateThemeProfilePane.java | 12 +- .../mainframe/toast/DesignerToastMsgUtil.java | 1 + .../background/image/ImageFileChooser.java | 4 +- .../fr/start/server/DesignEmbedHelper.java | 1 - .../theme/light/ui/laf/FineLaf.properties | 1 + .../storybook/components/ToastStoryBoard.java | 58 ++ .../replace/ui/ITReplaceMainDialog.java | 7 +- 16 files changed, 1506 insertions(+), 21 deletions(-) create mode 100755 designer-base/src/main/java/com/fine/component/toast/ToastMsgHolder.java create mode 100755 designer-base/src/main/java/com/fine/component/toast/ToastMsgManager.java create mode 100755 designer-base/src/main/java/com/fine/component/toast/ToastPane.java create mode 100755 designer-base/src/main/java/com/fine/theme/light/ui/ShadowBorder.java create mode 100755 designer-base/src/main/java/com/fine/theme/light/ui/ToastPanelUI.java create mode 100755 designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java create mode 100644 designer-base/src/test/java/com/fr/design/gui/storybook/components/ToastStoryBoard.java 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(); } From c3beb1631bea0e2c444d5f2af7a45576e6e47927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levy=2EXie-=E8=A7=A3=E5=AE=89=E6=A3=AE?= Date: Thu, 26 Dec 2024 19:54:56 +0800 Subject: [PATCH 4/5] =?UTF-8?q?REPORT-145112=20=E4=BB=A3=E7=A0=81=E8=B4=A8?= =?UTF-8?q?=E9=87=8F=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fine/component/popup/ComponentLayer.java | 68 ++++++---- .../fine/component/popup/GlassPaneChild.java | 12 ++ .../com/fine/component/popup/GlassPopup.java | 30 +++-- .../com/fine/component/popup/ImageChild.java | 8 +- .../fine/component/popup/WindowSnapshots.java | 36 ++--- .../com/fine/theme/light/ui/ShadowBorder.java | 5 +- .../com/fine/theme/light/ui/ToastPanelUI.java | 6 +- .../com/fine/theme/utils/ShadowRenderer.java | 123 +++++++++++------- 8 files changed, 180 insertions(+), 108 deletions(-) diff --git a/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java b/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java index a69ca0fe0a..ccd2b13be6 100755 --- a/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java +++ b/designer-base/src/main/java/com/fine/component/popup/ComponentLayer.java @@ -128,7 +128,9 @@ public class ComponentLayer extends JPanel { private void showComponent(GlassPaneChild component) { showSnapshot = true; this.nextComponent = component; - image = FineUIUtils.createImage(this.component); + if (!component.isTransparent()) { + image = FineUIUtils.createImage(this.component); + } if (component.getRoundBorder() > 0) { setOpaque(false); component.setOpaque(false); @@ -149,7 +151,9 @@ public class ComponentLayer extends JPanel { } simpleSnapshot = true; doLayout(); - image = FineUIUtils.createImage(component, component.getRoundBorder()); + if (!component.isTransparent()) { + image = FineUIUtils.createImage(component, component.getRoundBorder()); + } component.setVisible(false); } @@ -167,13 +171,15 @@ public class ComponentLayer extends JPanel { @Override protected void paintComponent(Graphics g) { - if (!isOpaque() && component.getRoundBorder() > 0) { - Graphics2D g2 = (Graphics2D) g.create(); - FlatUIUtils.setRenderingHints(g2); - g2.setColor(getBackground()); - int arc = UIScale.scale(component.getRoundBorder()); - FlatUIUtils.paintComponentBackground(g2, 0, 0, getWidth(), getHeight(), 0, arc); - g2.dispose(); + if (!component.isTransparent()) { + if (!isOpaque() && component.getRoundBorder() > 0) { + Graphics2D g2 = (Graphics2D) g.create(); + FlatUIUtils.setRenderingHints(g2); + g2.setColor(getBackground()); + int arc = UIScale.scale(component.getRoundBorder()); + FlatUIUtils.paintComponentBackground(g2, 0, 0, getWidth(), getHeight(), 0, arc); + g2.dispose(); + } } super.paintComponent(g); } @@ -193,26 +199,10 @@ public class ComponentLayer extends JPanel { g2.setColor(getBackground()); FlatUIUtils.paintComponentBackground(g2, 0, 0, width, height, 0, arc); if (image != null) { - int w = image.getWidth(null); - double x; - if (push) { - x = -w * animate; - } else { - x = w * animate; - } - g2.setComposite(AlphaComposite.SrcAtop.derive(1f - animate)); - g2.drawImage(image, (int) x, 0, null); + paintImage(g2); } if (nextImage != null) { - int w = nextImage.getWidth(null); - double x; - if (push) { - x = getWidth() - (w * animate); - } else { - x = -getWidth() + (w * animate); - } - g2.setComposite(AlphaComposite.SrcAtop.derive(animate)); - g2.drawImage(nextImage, (int) x, 0, null); + paintNextImage(g2); } g2.dispose(); g.drawImage(bufferedImage, 0, 0, null); @@ -222,6 +212,30 @@ public class ComponentLayer extends JPanel { } } + private void paintNextImage(Graphics2D g2) { + int w = nextImage.getWidth(null); + double x; + if (push) { + x = getWidth() - (w * animate); + } else { + x = -getWidth() + (w * animate); + } + g2.setComposite(AlphaComposite.SrcAtop.derive(animate)); + g2.drawImage(nextImage, (int) x, 0, null); + } + + private void paintImage(Graphics2D g2) { + int w = image.getWidth(null); + double x; + if (push) { + x = -w * animate; + } else { + x = w * animate; + } + g2.setComposite(AlphaComposite.SrcAtop.derive(1f - animate)); + g2.drawImage(image, (int) x, 0, null); + } + public GlassPaneChild getComponent() { return component; } diff --git a/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java b/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java index 4d248b5cf3..fc09817c31 100755 --- a/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java +++ b/designer-base/src/main/java/com/fine/component/popup/GlassPaneChild.java @@ -13,10 +13,21 @@ import javax.swing.JPanel; */ public class GlassPaneChild extends JPanel { + protected boolean transparent; + public int getRoundBorder() { return FlatUIUtils.getUIInt("Component.arc", 6); } + /** + * 组件是否透明 + * + * @return 组件透明 + */ + protected boolean isTransparent() { + return transparent; + } + /** * 放入组件时的回调方法 */ @@ -35,6 +46,7 @@ public class GlassPaneChild extends JPanel { * 显示弹窗组件时的回调方法 */ public void popupShow() { + } /** diff --git a/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java b/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java index 1159f35475..f681f76dab 100755 --- a/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java +++ b/designer-base/src/main/java/com/fine/component/popup/GlassPopup.java @@ -59,26 +59,34 @@ public class GlassPopup extends JComponent { @Override public void begin() { - componentLayer.showSnapshot(); - parent.windowSnapshots.createSnapshot(); - parent.contentPane.setVisible(false); + prepareAnimation(); } @Override public void end() { - componentLayer.hideSnapshot(); - if (show) { - componentLayer.getComponent().popupShow(); - } else { - parent.removePopup(GlassPopup.this); - } - parent.contentPane.setVisible(true); - parent.windowSnapshots.removeSnapshot(); + finishAnimation(); } }); animator.setInterpolator(CubicBezierEasing.EASE); } + private void prepareAnimation() { + componentLayer.showSnapshot(); + parent.windowSnapshots.createSnapshot(); + parent.contentPane.setVisible(false); + } + + private void finishAnimation() { + componentLayer.hideSnapshot(); + if (show) { + componentLayer.getComponent().popupShow(); + } else { + parent.removePopup(GlassPopup.this); + } + parent.contentPane.setVisible(true); + parent.windowSnapshots.removeSnapshot(); + } + public void setShowPopup(boolean show) { if (this.show != show) { // 前置动画未结束时,先关闭前置动画 diff --git a/designer-base/src/main/java/com/fine/component/popup/ImageChild.java b/designer-base/src/main/java/com/fine/component/popup/ImageChild.java index 5fd01ac63d..adf14061f1 100644 --- a/designer-base/src/main/java/com/fine/component/popup/ImageChild.java +++ b/designer-base/src/main/java/com/fine/component/popup/ImageChild.java @@ -11,7 +11,7 @@ import java.util.Objects; * 图片遮罩层,支撑静态图片及动态GIF * * @author Levy.Xie - * @since 11.0 + * @since 12.0 * Created on 2024/12/20 */ public class ImageChild extends GlassPaneChild { @@ -23,6 +23,12 @@ public class ImageChild extends GlassPaneChild { } public ImageChild(String imgPath) { + this(imgPath, true); + } + + public ImageChild(String imgPath, boolean transparent) { + this.transparent = transparent; + setLayout(new BorderLayout()); setBorder(new ScaledEmptyBorder(8, 8, 8, 8)); ImageIcon icon = new ImageIcon(Objects.requireNonNull(getClass().getClassLoader().getResource(imgPath))); diff --git a/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java b/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java index 6ecfe471dd..409acc15ea 100755 --- a/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java +++ b/designer-base/src/main/java/com/fine/component/popup/WindowSnapshots.java @@ -1,5 +1,7 @@ package com.fine.component.popup; +import org.jetbrains.annotations.NotNull; + import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.JRootPane; @@ -39,21 +41,7 @@ public class WindowSnapshots { JLayeredPane layeredPane = getLayeredPane(); if (layeredPane != null) { layeredPane.paint(snapshot.getGraphics()); - snapshotLayer = new JComponent() { - @Override - public void paint(Graphics g) { - if (snapshot.contentsLost()) { - return; - } - g.drawImage(snapshot, 0, 0, null); - } - - @Override - public void removeNotify() { - super.removeNotify(); - snapshot.flush(); - } - }; + snapshotLayer = createSnapLayer(snapshot); snapshotLayer.setSize(layeredPane.getSize()); layeredPane.add(snapshotLayer, Integer.valueOf(JLayeredPane.DRAG_LAYER + 1)); } @@ -61,6 +49,24 @@ public class WindowSnapshots { } } + private static @NotNull JComponent createSnapLayer(VolatileImage snapshot) { + return new JComponent() { + @Override + public void paint(Graphics g) { + if (snapshot.contentsLost()) { + return; + } + g.drawImage(snapshot, 0, 0, null); + } + + @Override + public void removeNotify() { + super.removeNotify(); + snapshot.flush(); + } + }; + } + /** * 移除窗口快照覆盖层 */ 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 index 16cbad12a0..637879f018 100755 --- 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 @@ -5,6 +5,7 @@ import com.formdev.flatlaf.FlatPropertiesLaf; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.util.UIScale; +import com.fr.stable.AssistUtils; import javax.swing.JComponent; import javax.swing.border.EmptyBorder; @@ -73,7 +74,9 @@ public class ShadowBorder extends EmptyBorder { 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) { + if (shadowImage == null || !shadowColor.equals(lastShadowColor) + || width != lastWidth || height != lastHeight || shadowSize != lastShadowSize + || !AssistUtils.equals(shadowOpacity, lastShadowOpacity) || arc != lastArc) { shadowImage = createShadowImage(width, height, arc); lastShadowColor = shadowColor; lastWidth = width; 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 index dd079693a1..ad0dcdc8bf 100755 --- 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 @@ -136,24 +136,22 @@ public class ToastPanelUI extends BasicPanelUI implements StyleableUI, PropertyC @Override public void propertyChange(PropertyChangeEvent e) { + JPanel c = (JPanel) e.getSource(); 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); @@ -161,7 +159,6 @@ public class ToastPanelUI extends BasicPanelUI implements StyleableUI, PropertyC } case STYLE: case STYLE_CLASS: { - JPanel c = (JPanel) e.getSource(); installStyle(c); refreshUI(c); break; @@ -174,7 +171,6 @@ public class ToastPanelUI extends BasicPanelUI implements StyleableUI, PropertyC c.repaint(); } - private void installIconComponent(JComponent c) { iconComponent = clientProperty(c, TOAST_ICON, null, JComponent.class); if (iconComponent != null) { 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 index 801c4ef8b2..58e3b90046 100755 --- a/designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java +++ b/designer-base/src/main/java/com/fine/theme/utils/ShadowRenderer.java @@ -1,5 +1,7 @@ package com.fine.theme.utils; +import org.jetbrains.annotations.NotNull; + import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; @@ -77,86 +79,111 @@ public class ShadowRenderer { 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[] hSumLookup = initSumLookupArray(1.0f, shadowSize); + int[] vSumLookup = initSumLookupArray(opacity, shadowSize); + + applyHorizontalShadowBlur(left, dstWidth, srcHeight, shadowSize, aHistory, srcWidth, hSumLookup, dstBuffer, srcBuffer); + + applyVerticalShadowBlur(dstWidth, left, aHistory, right, dstBuffer, yStop, vSumLookup, shadowRgb, + lastPixelOffset, shadowSize, dstHeight); + + setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer); + return dst; + } + + private static int @NotNull [] initSumLookupArray(float x, int shadowSize) { + float sumDivider = x / shadowSize; + int[] sumLookup = new int[256 * shadowSize]; + for (int i = 0; i < sumLookup.length; i++) { + sumLookup[i] = (int) (i * sumDivider); } - 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; + return sumLookup; + } + + private static void applyVerticalShadowBlur(int dstWidth, int left, int[] aHistory, int right, int[] dstBuffer, + int yStop, int[] vSumLookup, int shadowRgb, int lastPixelOffset, + int shadowSize, int dstHeight) { + for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { + int aSum = initializeAlphaSum(dstWidth, left, aHistory, right, dstBuffer, bufferOffset, 0); + bufferOffset = x; + int historyIdx = 0; + for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) { + int a = vSumLookup[aSum]; + dstBuffer[bufferOffset] = a << 24 | shadowRgb; aSum -= aHistory[historyIdx]; - a = srcBuffer[srcOffset + srcX] >>> 24; + a = dstBuffer[bufferOffset + lastPixelOffset] >>> 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; + for (int y = yStop; y < dstHeight; y++, bufferOffset += dstWidth) { + int a = vSumLookup[aSum]; + dstBuffer[bufferOffset] = a << 24 | shadowRgb; aSum -= aHistory[historyIdx]; if (++historyIdx >= shadowSize) { historyIdx -= shadowSize; } } } + } + + private static int initializeAlphaSum(int dstWidth, int left, int[] aHistory, int right, int[] dstBuffer, int bufferOffset, int aSum) { + int historyIdx; + for (historyIdx = 0; historyIdx < left; ) { + aHistory[historyIdx++] = 0; + } + for (int y = 0; y < right; y++, bufferOffset += dstWidth) { + int a = dstBuffer[bufferOffset] >>> 24; + aHistory[historyIdx++] = a; + aSum += a; + } + return aSum; + } - for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x) { - aSum = 0; - for (historyIdx = 0; historyIdx < left; ) { + private static void applyHorizontalShadowBlur(int left, int dstWidth, int srcHeight, int shadowSize, + int[] aHistory, int srcWidth, int[] hSumLookup, int[] dstBuffer, int[] srcBuffer) { + int historyIdx; + for (int srcY = 0, dstOffset = left * dstWidth; srcY < srcHeight; srcY++) { + for (historyIdx = 0; historyIdx < shadowSize; ) { aHistory[historyIdx++] = 0; } - for (int y = 0; y < right; y++, bufferOffset += dstWidth) { - int a = dstBuffer[bufferOffset] >>> 24; - aHistory[historyIdx++] = a; - aSum += a; - } - bufferOffset = x; + int aSum = 0; historyIdx = 0; - for (int y = 0; y < yStop; y++, bufferOffset += dstWidth) { - int a = vSumLookup[aSum]; - dstBuffer[bufferOffset] = a << 24 | shadowRgb; + int srcOffset = srcY * srcWidth; + for (int srcX = 0; srcX < srcWidth; srcX++) { + int a = hSumLookup[aSum]; + dstBuffer[dstOffset++] = a << 24; aSum -= aHistory[historyIdx]; - a = dstBuffer[bufferOffset + lastPixelOffset] >>> 24; + a = srcBuffer[srcOffset + srcX] >>> 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; - } + dstOffset = updateDstBufferOffset(shadowSize, aHistory, hSumLookup, dstBuffer, aSum, dstOffset, historyIdx); + } + } + + private static int updateDstBufferOffset(int shadowSize, int[] aHistory, int[] hSumLookup, int[] dstBuffer, + int aSum, int dstOffset, int historyIdx) { + for (int i = 0; i < shadowSize; i++) { + int a = hSumLookup[aSum]; + dstBuffer[dstOffset++] = a << 24; + aSum -= aHistory[historyIdx]; + if (++historyIdx >= shadowSize) { + historyIdx -= shadowSize; } } - setPixels(dst, 0, 0, dstWidth, dstHeight, dstBuffer); - return dst; + return dstOffset; } private int[] getPixels(BufferedImage img, int x, int y, int w, int h, int[] pixels) { From 38abeab375683495e413fbf0d7448084e97ecbf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Levy=2EXie-=E8=A7=A3=E5=AE=89=E6=A3=AE?= Date: Mon, 30 Dec 2024 09:16:55 +0800 Subject: [PATCH 5/5] =?UTF-8?q?=E6=97=A0jira=20=E8=A7=A3=E5=86=B3=E5=86=B2?= =?UTF-8?q?=E7=AA=81=E6=BC=8F=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/fr/design/file/DefaultTemplateTreeDefineProcessor.java | 1 - 1 file changed, 1 deletion(-) 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 ee26638f98..77011055ce 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 @@ -402,7 +402,6 @@ public class DefaultTemplateTreeDefineProcessor extends AbstractTemplateTreeDefi dispose(); if (moveSuccess) { ToastMsgManager.getInstance().info(Toolkit.i18nText("Fine-Design_Basic_Template_Moved_Success")); - DesignerFrameFileDealerPane.getInstance().getSelectedOperation().refresh(); NodeAuthProcessor.getInstance().refresh(); DesignerFrameFileDealerPane.getInstance().refreshRightToolBarBy(null); LocateAction.gotoEditingTemplateLeaf(targetFile);