package com.fr.startup.ui; import com.fr.base.svg.IconUtils; import com.fr.design.components.tooltip.ModernToolTip; import com.fr.design.gui.icontainer.UIScrollPane; import com.fr.design.gui.ilable.UILabel; import com.fr.design.gui.itextfield.FRGraphics2D; import com.fr.design.i18n.Toolkit; import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.utils.ColorUtils; import com.fr.start.common.DesignerStartupContext; import com.fr.startup.metric.DesignerMetrics; import com.fr.third.guava.collect.Lists; import org.jetbrains.annotations.NotNull; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JToolTip; import javax.swing.JViewport; import javax.swing.RepaintManager; import javax.swing.ScrollPaneConstants; import javax.swing.border.EmptyBorder; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.Image; import java.awt.RenderingHints; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.ImageObserver; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import static com.fr.startup.ui.StartupPageConstants.ARC_DIAMETER; /** * created by Harrison on 2022/07/06 **/ public class StartupPageWorkspacePanel extends JPanel { /* color */ private static final Color SHALLOW_WHITE_COLOR = new Color(248, 250, 254); private static final Color HOVER_COLOR = new Color(65, 155, 249); private static final Color PATH_COLOR = new Color(51, 51, 52, (int) Math.round(255 * 0.5)); /* 长度 */ private static final int SCROLL_BAR_WIDTH = 20; private static final int CONTENT_WIDTH = StartupPageConstants.CONTENT_WIDTH + SCROLL_BAR_WIDTH; private static final int BORDER_THIN = 2; private static final int ITEM_VERTICAL_GAP = 20; private static final int SINGLE_ITEM_HEIGHT = 72; private static final int SCROLL_HEIGHT = SINGLE_ITEM_HEIGHT * 4 + ITEM_VERTICAL_GAP * 4; private static final int NAME_LABEL_SIZE = 15; private static final int PATH_LABEL_SIZE = 10; private static final Dimension LABEL_DIMENSION = new Dimension(28, 28); private static final Dimension PATH_DIMENSION = new Dimension(100, 20); private static final Dimension SELECT_WORKSPACE_DIMENSION = new Dimension(210, 72); private static final Dimension SELECT_CREATE_DIMENSION = new Dimension(60, 72); private static final int COLUMN_LIMIT = 3; private static final int DOUBLE_CLICK_COUNT = 2; public static final int PARTITION_LIMIT = 2; /* model */ private final StartupPageModel pageModel; private final List> partitions; private Runnable selectWorkspaceRunnable; private final Runnable createNewTemplateRunnable; private final Runnable openEmptyTemplateRunnable; private JComponent contentPanel; private JPanel tailPanel; private boolean showMore = true; public StartupPageWorkspacePanel(StartupPageModel pageModel) { this.setLayout(new BorderLayout(0, 0)); this.setBorder(new EmptyBorder(15, 0, 20, 0)); this.pageModel = pageModel; List workspaceInfos = pageModel.getWorkspaceInfos(); this.partitions = Lists.partition(workspaceInfos, COLUMN_LIMIT); this.contentPanel = generateLimitContentPanel(partitions); this.add(contentPanel, BorderLayout.NORTH); if (partitions.size() > PARTITION_LIMIT) { this.tailPanel = generateTailPanel(); this.add(tailPanel, BorderLayout.SOUTH); } this.createNewTemplateRunnable = pageModel.getCreateNewTemplateRunnable(); this.openEmptyTemplateRunnable = pageModel.getOpenEmptyTemplateRunnable(); this.repaint(); } public void showLessContent() { this.remove(this.contentPanel); this.contentPanel = generateLimitContentPanel(this.partitions); this.add(contentPanel, BorderLayout.NORTH); } public void showMoreContent() { this.remove(this.contentPanel); this.contentPanel = generateUnLimitContentPanel(this.partitions); this.add(contentPanel, BorderLayout.NORTH); } private JComponent generateUnLimitContentPanel(List> partitions) { JComponent panel = generateUnLimitContentPanel0(partitions); ColorUtils.transparentBackground(panel); return panel; } private JComponent generateUnLimitContentPanel0(List> partitions) { JPanel workspaceDescWrapper = new JPanel(); workspaceDescWrapper.setLayout(new BorderLayout(0, 0)); workspaceDescWrapper.setBorder(new EmptyBorder(0, 0, 0, 0)); JPanel workspaceDescPanel = new JPanel(); workspaceDescPanel.setLayout(new GridLayout(partitions.size(), 1, 0, 0)); for (List partition : partitions) { JPanel partitionPanel = generatePartitionPanel(partition); workspaceDescPanel.add(partitionPanel); } boolean needScroll = partitions.size() > 4; if (needScroll) { return generateScrollUnLimitContentPanel(workspaceDescWrapper, workspaceDescPanel); } workspaceDescWrapper.add(workspaceDescPanel, BorderLayout.CENTER); return workspaceDescWrapper; } @NotNull private JPanel generateScrollUnLimitContentPanel(JPanel workspaceDescWrapper, JPanel workspaceDescPanel) { // 滚动条 UIScrollPane scrollPane = new UIScrollPane(workspaceDescPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); JViewport viewport = scrollPane.getViewport(); JViewport scrollViewport = new TransparentScrollViewPort(); // 动态画图 scrollViewport.addChangeListener(e -> repaintAll()); scrollViewport.setView(viewport.getView()); scrollPane.setViewport(scrollViewport); scrollPane.setBorder(new EmptyBorder(10, 0, 0, 0)); scrollPane.setPreferredSize(new Dimension(CONTENT_WIDTH, SCROLL_HEIGHT)); workspaceDescWrapper.add(scrollPane, BorderLayout.CENTER); return workspaceDescWrapper; } private JPanel generateLimitContentPanel(List> partitions) { JPanel workspaceDescPanel = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.LEFT, 0, 0); int limit = 2; for (int i = 0; i < partitions.size(); i++) { if (i >= limit) { break; } List partition = partitions.get(i); JPanel partitionPanel = generatePartitionPanel(partition); workspaceDescPanel.add(partitionPanel); } ColorUtils.transparentBackground(workspaceDescPanel); return workspaceDescPanel; } @NotNull private JPanel generateTailPanel() { AtomicReference hoverBackColorRef = new AtomicReference<>(); JPanel tailPanel = new JPanel(); { tailPanel.setLayout(new FlowLayout(FlowLayout.RIGHT)); tailPanel.setBorder(new EmptyBorder(0, 0, 0, 20)); JPanel showAllPanel = new JPanel() { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (hoverBackColorRef.get() != null) { g.setColor(hoverBackColorRef.get()); Dimension preferredSize = getPreferredSize(); g.fillRoundRect(0, 0, preferredSize.width, preferredSize.height, 5, 5); } } }; showAllPanel.setLayout(new BorderLayout(5, 0)); showAllPanel.setBorder(new EmptyBorder(5, 5, 5, 5)); UILabel fontLabel = new UILabel(Toolkit.i18nText("Fine-Design_Startup_Page_Expand_All")); fontLabel.setForeground(HOVER_COLOR); showAllPanel.setBackground(new Color(0, 0, 0, 0)); showAllPanel.add(fontLabel, BorderLayout.WEST); UILabel iconLabel = new UILabel(IconUtils.readIcon("/com/fr/design/startup/show_more.svg")); showAllPanel.add(iconLabel, BorderLayout.EAST); Color showAllBackground = showAllPanel.getBackground(); showAllPanel.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { Color hoverBackColor = new Color(217, 235, 254); hoverBackColorRef.set(hoverBackColor); repaintAll(); } @Override public void mouseExited(MouseEvent e) { hoverBackColorRef.set(null); ColorUtils.syncBackground(showAllPanel, showAllBackground); repaintAll(); } @Override public void mousePressed(MouseEvent e) { doShowAllAction(fontLabel, iconLabel); } }); tailPanel.add(showAllPanel); Dimension preferredSize = tailPanel.getPreferredSize(); tailPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, (int) preferredSize.getHeight())); } return tailPanel; } @NotNull private JPanel generatePartitionPanel(List partition) { JPanel partitionPanelWrapper = new JPanel(); partitionPanelWrapper.setBorder(new EmptyBorder(10,0,10,0)); partitionPanelWrapper.setLayout(new BorderLayout()); JPanel partitionPanel = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(0, 20, 0); partitionPanel.setName("partitionPanel"); for (StartupWorkspaceBean workspaceInfo : partition) { JPanel workspaceItemDesc = FRGUIPaneFactory.createBorderLayout_S_Pane(); layoutSelectWorkspacePanel(workspaceInfo, workspaceItemDesc); layoutSelectAndCreatePanel(workspaceInfo, workspaceItemDesc); partitionPanel.add(workspaceItemDesc); Dimension preferredSize = partitionPanel.getPreferredSize(); partitionPanel.setPreferredSize(new Dimension(CONTENT_WIDTH, (int) preferredSize.getHeight())); } partitionPanelWrapper.add(partitionPanel, BorderLayout.CENTER); return partitionPanelWrapper; } private void layoutSelectWorkspacePanel(StartupWorkspaceBean workspaceInfo, JPanel workspaceItemDesc) { // 选择工作目录 // 图标 / 分隔符 / 说明-进入 // 选择并新建 AtomicReference borderColorRef = new AtomicReference<>(null); JPanel selectWorkspacePanel = new JPanel() { @Override public JToolTip createToolTip() { return new ModernToolTip(); } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color borderColor = borderColorRef.get(); Color backColor = Color.WHITE; g2d.setColor(backColor); // 直角和圆角上下叠合在一起 int rectOffset = 10; int roundOffset = 15; // 填充一个直角 g2d.fillRoundRect(0, 0, getWidth() - rectOffset, getHeight(), ARC_DIAMETER, ARC_DIAMETER); // 填充一个圆角 g2d.fillRoundRect(getWidth() - roundOffset, 0, roundOffset, getHeight(), 0, 0); paintBorderIfHover(g2d, borderColor, backColor); } /** * 当悬浮的时候,将边框展示出来 * 会叠合,然后填充中间 * * |----【-|--】 * | A 【C| B】 * |----【-|--】 * * 见上面有两种线,分别是 A-| 和 B-【 * 这里会将 A 和 B 叠在一起,中间则存在 C,然后将 C 的部分扩大一点,然后填充上背景 * 则形成下面这种图形 * * |----】 * |----】 * * @param g2d 绘画 * @param borderColor 边框颜色 * @param backColor 背景颜色 */ private void paintBorderIfHover(Graphics2D g2d, Color borderColor, Color backColor) { if (borderColor != null) { g2d.setColor(borderColor); g2d.setStroke(new BasicStroke(BORDER_THIN)); // 需要一个修正值 int strokeOffset = BORDER_THIN / 2; // 直角和圆角上下叠合在一起 int rectOffset = 10; int roundOffset = 15; // 画一个圆角 int fixRoundWidth = getWidth() - rectOffset; int fixRoundHeight = getHeight() - BORDER_THIN; g2d.drawRoundRect(strokeOffset, strokeOffset, fixRoundWidth, fixRoundHeight, ARC_DIAMETER, ARC_DIAMETER); g2d.setColor(backColor); // 绘制一个矩形,覆盖住多余的相交线 // 需要考虑上下的线宽 int coverHeight = getHeight() - (BORDER_THIN * 2); // 偏左一点的 fixedX int fixedX = getWidth() - roundOffset - BORDER_THIN; // 圆角和直角相交的区域 int coverWidth = 15; g2d.fillRect(fixedX, BORDER_THIN, coverWidth, coverHeight); g2d.setColor(borderColor); g2d.drawLine(getWidth() / 2, strokeOffset, getWidth(), strokeOffset); g2d.drawLine(getWidth() / 2, getHeight() - strokeOffset, getWidth(), getHeight() - strokeOffset); g2d.drawLine(getWidth() - strokeOffset, strokeOffset, getWidth() - strokeOffset, getHeight() - strokeOffset); } } }; selectWorkspacePanel.setLayout(new BorderLayout(0,0)); selectWorkspacePanel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Double_Click_Enter_Workspace")); selectWorkspacePanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); { JPanel iconPanel = FRGUIPaneFactory.createBorderLayout_L_Pane(); iconPanel.setBorder(new EmptyBorder(0, 10, 0, 10)); Icon icon = StartupPageUtil.getIcon4DescAreaByWorkspace(workspaceInfo); UILabel label = new UILabel(icon); label.setPreferredSize(LABEL_DIMENSION); iconPanel.add(label, BorderLayout.CENTER); selectWorkspacePanel.add(iconPanel, BorderLayout.WEST); // desc / >箭头 JPanel descPanel = new JPanel(); descPanel.setLayout(FRGUIPaneFactory.createM_BorderLayout()); descPanel.setBorder(new EmptyBorder(0, 10, 0, 0)); JPanel simpleDescPanelWrapper = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, FlowLayout.CENTER, 0, 0); JPanel simpleDescPanel = FRGUIPaneFactory.createBorderLayout_S_Pane(); UILabel nameLabel = new UILabel(workspaceInfo.getName()); Font font = nameLabel.getFont(); Font newSizeFont = font.deriveFont(font.getStyle(), NAME_LABEL_SIZE); nameLabel.setFont(newSizeFont); nameLabel.setPreferredSize(PATH_DIMENSION); Color nameForeground = nameLabel.getForeground(); simpleDescPanel.add(nameLabel,BorderLayout.NORTH); UILabel pathLabel = new UILabel(workspaceInfo.getPath()); pathLabel.setPreferredSize(PATH_DIMENSION); Font pathFont = pathLabel.getFont(); pathLabel.setFont(pathFont.deriveFont(pathFont.getStyle(), PATH_LABEL_SIZE)); Color pathColor = PATH_COLOR; pathLabel.setForeground(pathColor); simpleDescPanel.add(pathLabel, BorderLayout.SOUTH); simpleDescPanelWrapper.add(simpleDescPanel); descPanel.add(simpleDescPanelWrapper, BorderLayout.WEST); MouseAdapter selectWorkspaceMouseListener = new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { Color hoverColor = HOVER_COLOR; borderColorRef.set(hoverColor); nameLabel.setForeground(hoverColor); pathLabel.setForeground(hoverColor ); repaintAll(); } @Override public void mouseExited(MouseEvent e) { borderColorRef.set(Color.WHITE); nameLabel.setForeground(nameForeground); pathLabel.setForeground(pathColor); repaintAll(); } @Override public void mousePressed(MouseEvent e) { int clickCount = e.getClickCount(); if (clickCount == DOUBLE_CLICK_COUNT) { doOpenEmptyTemplate(workspaceInfo); return; } doSwitchWorkspace(workspaceInfo); } }; UILabel arrowLabel = new UILabel(IconUtils.readIcon("/com/fr/design/startup/more.svg")) { @Override public JToolTip createToolTip() { return new ModernToolTip(); } }; arrowLabel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Enter_Workspace")); arrowLabel.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { arrowLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/more_hover.svg")); selectWorkspaceMouseListener.mouseEntered(e); } @Override public void mouseExited(MouseEvent e) { arrowLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/more.svg")); selectWorkspaceMouseListener.mouseExited(e); } @Override public void mousePressed(MouseEvent e) { doOpenEmptyTemplate(workspaceInfo); } }); descPanel.add(arrowLabel, BorderLayout.EAST); selectWorkspacePanel.add(descPanel, BorderLayout.CENTER); selectWorkspacePanel.addMouseListener(selectWorkspaceMouseListener); } ColorUtils.syncBackground(selectWorkspacePanel, Color.WHITE); selectWorkspacePanel.setPreferredSize(SELECT_WORKSPACE_DIMENSION); workspaceItemDesc.add(selectWorkspacePanel, BorderLayout.WEST); } private void layoutSelectAndCreatePanel(StartupWorkspaceBean workspaceInfo, JPanel workspaceItemDesc) { // 选择并新建 AtomicReference borderColorRef = new AtomicReference<>(null); JPanel selectAndCreatePanel = new JPanel() { @Override public JToolTip createToolTip() { return new ModernToolTip(); } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color borderColor = borderColorRef.get(); Color backColor = SHALLOW_WHITE_COLOR; g2d.setColor(backColor); // 见 layoutSelectWorkspacePanel 部分的分析 // 直角和圆角上下叠合在一起 int rectOffset = 10; int roundOffset = 15; int strokeOffset = BORDER_THIN / 2; int fixedRoundOffset = roundOffset + strokeOffset; g2d.fillRoundRect(0, 0, getWidth() - rectOffset, getHeight(), 0, 0); g2d.fillRoundRect(getWidth() - fixedRoundOffset, 0, roundOffset, getHeight(), ARC_DIAMETER, ARC_DIAMETER); if (borderColor != null) { g2d.setColor(borderColor); g2d.setStroke(new BasicStroke(BORDER_THIN)); int borderOffset = BORDER_THIN * 2; // 画画的笔触需要调整一下 g2d.drawRoundRect(strokeOffset, strokeOffset, getWidth() - borderOffset, getHeight() - BORDER_THIN, ARC_DIAMETER, ARC_DIAMETER); g2d.setColor(backColor); int fillWidth = 15; g2d.fillRect(0, 0, fillWidth, getHeight()); g2d.setColor(borderColor); g2d.drawLine(strokeOffset, strokeOffset, fillWidth, strokeOffset); g2d.drawLine(strokeOffset, getHeight() - strokeOffset, fillWidth, getHeight() - strokeOffset); g2d.drawLine(strokeOffset, strokeOffset, strokeOffset, getHeight() - strokeOffset); } } }; selectAndCreatePanel.setToolTipText(Toolkit.i18nText("Fine-Design_Startup_Page_Enter_Workspace_And_Create")); selectAndCreatePanel.setBorder(new EmptyBorder(0, 0, 0, 0)); selectAndCreatePanel.setLayout(new BorderLayout()); { UILabel label = new UILabel(IconUtils.readIcon("/com/fr/design/standard/system/add")); label.setPreferredSize(new Dimension(ARC_DIAMETER, ARC_DIAMETER)); label.setForeground(HOVER_COLOR); selectAndCreatePanel.add(label, BorderLayout.CENTER); selectAndCreatePanel.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { borderColorRef.set(HOVER_COLOR); label.setIcon(IconUtils.readIcon("/com/fr/design/standard/system/add_hover.svg")); repaintAll(); } @Override public void mouseExited(MouseEvent e) { borderColorRef.set(null); label.setIcon(IconUtils.readIcon("/com/fr/design/standard/system/add")); repaintAll(); } @Override public void mousePressed(MouseEvent e) { pageModel.setSelectWorkspaceInfo(workspaceInfo); createNewTemplateRunnable.run(); } }); } ColorUtils.syncBackground(selectAndCreatePanel, Color.GREEN); selectAndCreatePanel.setPreferredSize(SELECT_CREATE_DIMENSION); workspaceItemDesc.add(selectAndCreatePanel, BorderLayout.EAST); } public void setSelectWorkspaceRunnable(Runnable selectWorkspaceRunnable) { this.selectWorkspaceRunnable = selectWorkspaceRunnable; } private void doOpenEmptyTemplate(StartupWorkspaceBean workspaceInfo) { pageModel.setSelectWorkspaceInfo(workspaceInfo); openEmptyTemplateRunnable.run(); DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics(); designerMetrics.getStatistic().recordOpenEmptyTemplate(); } private void doSwitchWorkspace(StartupWorkspaceBean workspaceInfo) { StartupWorkspaceBean lastWorkspaceInfo = pageModel.getSelectWorkspaceInfo(); // selectWorkspaceRunnable pageModel.setSelectWorkspaceInfo(workspaceInfo); selectWorkspaceRunnable.run(); DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics(); designerMetrics.getStatistic().recordSwitchWorkspace(lastWorkspaceInfo, workspaceInfo); } private void doShowAllAction(UILabel fontLabel, UILabel iconLabel) { if (showMore) { fontLabel.setText(Toolkit.i18nText("Fine-Design_Startup_Page_Collapse_Workspace")); iconLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/show_less.svg")); showMoreContent(); showMore = !showMore; } else { fontLabel.setText(Toolkit.i18nText("Fine-Design_Startup_Page_Expand_All")); iconLabel.setIcon(IconUtils.readIcon("/com/fr/design/startup/show_more.svg")); showLessContent(); showMore = !showMore; } DesignerMetrics designerMetrics = DesignerStartupContext.getInstance().getDesignerMetrics(); designerMetrics.getStatistic().recordShowAllAction(); repaintAll(); } private void repaintAll() { this.getRootPane().repaint(); } /** * 支持透明的滚动视图 */ private class TransparentScrollViewPort extends JViewport { /** * 从而屏蔽掉 {@link RepaintManager.PaintManager#paintDoubleBuffered(JComponent, Image, Graphics, int, int, int, int)} * * @return 创建一个不会实际画图的 Graphics */ @Override public Graphics getGraphics() { Graphics graphics = super.getGraphics(); return new FRGraphics2D((Graphics2D) graphics) { @Override public boolean drawImage(Image img, int x, int y, ImageObserver observer) { return true; } }; } } }