From d9dbfb3db11c81cf51db963d59be396b7e289413 Mon Sep 17 00:00:00 2001 From: kuangshuai Date: Sun, 26 Sep 2021 21:52:26 +0800 Subject: [PATCH] =?UTF-8?q?REPORT-59629=20=E5=BC=95=E5=AF=BC=E9=A1=B5?= =?UTF-8?q?=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mainframe/NorthRegionContainerPane.java | 1 + .../fr/design/mainframe/guide/base/Guide.java | 160 ++++++++ .../mainframe/guide/base/GuideBuilder.java | 46 +++ .../mainframe/guide/base/GuideGroup.java | 51 +++ .../mainframe/guide/base/GuideLifecycle.java | 32 ++ .../guide/base/GuideLifecycleAdaptor.java | 28 ++ .../mainframe/guide/base/GuideManager.java | 88 ++++ .../mainframe/guide/base/GuideView.java | 132 ++++++ .../guide/collect/GuideCollector.java | 155 +++++++ .../guide/scene/AbstractGuideScene.java | 386 ++++++++++++++++++ .../mainframe/guide/scene/ClickScene.java | 90 ++++ .../mainframe/guide/scene/DisplayScene.java | 33 ++ .../mainframe/guide/scene/DragScene.java | 63 +++ .../mainframe/guide/scene/GuideScene.java | 18 + .../guide/scene/GuideSceneLifecycle.java | 20 + .../scene/GuideSceneLifecycleAdaptor.java | 18 + .../mainframe/guide/scene/SceneFilter.java | 5 + .../drag/DragAndDropDragGestureListener.java | 60 +++ .../design/mainframe/guide/tip/BubbleTip.java | 83 ++++ .../design/mainframe/guide/tip/GuideTip.java | 14 + .../design/mainframe/guide/ui/BubbleHint.java | 74 ++++ .../mainframe/guide/ui/BubbleHintDialog.java | 30 ++ .../design/mainframe/guide/ui/ExpandPane.java | 78 ++++ .../guide/ui/GuideCompleteDialog.java | 117 ++++++ .../mainframe/guide/ui/GuideManageDialog.java | 226 ++++++++++ .../mainframe/guide/ui/bubble/Bubble.java | 152 +++++++ .../guide/ui/bubble/BubbleWithClose.java | 255 ++++++++++++ .../mainframe/guide/utils/ScreenImage.java | 98 +++++ .../fr/design/mainframe/guide/arrow_down.png | Bin 0 -> 162 bytes .../fr/design/mainframe/guide/arrow_right.png | Bin 0 -> 153 bytes .../com/fr/design/mainframe/guide/close.png | Bin 0 -> 375 bytes .../design/mainframe/guide/complete_all.png | Bin 0 -> 485 bytes .../design/mainframe/guide/complete_none.png | Bin 0 -> 401 bytes .../design/mainframe/guide/complete_some.png | Bin 0 -> 567 bytes .../com/fr/design/mainframe/guide/guide.png | Bin 0 -> 1470 bytes .../com/fr/design/mainframe/guide/success.png | Bin 0 -> 31304 bytes .../mainframe/guide/entry/GuideEntryPane.java | 79 ++++ .../main/java/com/fr/start/MainDesigner.java | 5 + 38 files changed, 2597 insertions(+) create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/Guide.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideBuilder.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideGroup.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycle.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycleAdaptor.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideManager.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideView.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/collect/GuideCollector.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/AbstractGuideScene.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/ClickScene.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DisplayScene.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DragScene.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideScene.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycle.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycleAdaptor.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/SceneFilter.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/scene/drag/DragAndDropDragGestureListener.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/tip/BubbleTip.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/tip/GuideTip.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHint.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHintDialog.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/ExpandPane.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideCompleteDialog.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideManageDialog.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/Bubble.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/BubbleWithClose.java create mode 100644 designer-base/src/main/java/com/fr/design/mainframe/guide/utils/ScreenImage.java create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_down.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_right.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/close.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/complete_all.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/complete_none.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/complete_some.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/guide.png create mode 100644 designer-base/src/main/resources/com/fr/design/mainframe/guide/success.png create mode 100644 designer-realize/src/main/java/com/fr/design/mainframe/guide/entry/GuideEntryPane.java diff --git a/designer-base/src/main/java/com/fr/design/mainframe/NorthRegionContainerPane.java b/designer-base/src/main/java/com/fr/design/mainframe/NorthRegionContainerPane.java index b070710579..f15b3517f0 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/NorthRegionContainerPane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/NorthRegionContainerPane.java @@ -116,6 +116,7 @@ public class NorthRegionContainerPane extends JPanel { if (!DesignerEnvManager.getEnvManager().getAlphaFineConfigManager().isEnabled()) { ad.createAlphaFinePane().setVisible(false); } + northEastPane.add(ad.createGuideEntryPane()); northEastPane.add(ad.createNotificationCenterPane()); OSSupportCenter.buildAction(new OSBasedAction() { diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/Guide.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/Guide.java new file mode 100644 index 0000000000..003f229088 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/Guide.java @@ -0,0 +1,160 @@ +package com.fr.design.mainframe.guide.base; + +import com.fr.design.mainframe.guide.collect.GuideCollector; +import com.fr.design.mainframe.guide.scene.GuideScene; +import com.fr.design.mainframe.guide.ui.GuideCompleteDialog; +import com.fr.stable.StringUtils; + +import javax.swing.SwingUtilities; + +public class Guide { + public enum GuideState { + NONE, DONE + } + private String id; + private String name; + private String description; + private GuideState state; + private GuideView guideView; + private GuideLifecycle lifecycle; + private boolean isComplete; + private GuideScene scene; + + public Guide() { + this(null, null, null); + } + + public Guide(String id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + this.state = GuideState.NONE; + } + + public String getID() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + if (StringUtils.isNotEmpty(description)) { + return description; + } + return name; + } + + public GuideState getState() { + return state; + } + + public void setState(GuideState state) { + this.state = state; + } + + public void setGuideView(GuideView guideView) { + this.guideView = guideView; + } + + public GuideView getGuideView() { + return guideView; + } + + public void setComplete(boolean complete) { + isComplete = complete; + } + + public boolean isComplete() { + return isComplete; + } + + public void setScene(GuideScene scene) { + this.scene = scene; + } + + public GuideScene getScene() { + return scene; + } + + /** + * 开启引导流程 + */ + public void go() { + try { + if (lifecycle != null && !lifecycle.prepared()) { + return; + } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + start(); + } + }); + } catch (Exception e) { + e.printStackTrace(); + end(); + } + + } + + public void start() { + if (scene != null) { + guideView.setScene(scene); + guideView.showGuide(); + GuideManager.getInstance().setCurrentGuide(this); + if (lifecycle != null) { + lifecycle.onStart(); + } + } else { + complete(); + } + + } + + public void complete() { + if (lifecycle != null) { + lifecycle.onComplete(); + } + setComplete(true); + GuideCollector.getInstance().saveInfo(); + end(); + GuideCompleteDialog.getInstance().showDialog(getName()); + } + + public void terminate() { + if (lifecycle != null) { + lifecycle.onTerminate(); + } + end(); + } + + public void end() { + GuideManager.getInstance().setCurrentGuide(null); + guideView.dismissGuide(); + if (lifecycle != null) { + lifecycle.onEnd(); + } + } + + public void registerLifecycle(GuideLifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + public void removeGuideLifecycle() { + this.lifecycle = null; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideBuilder.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideBuilder.java new file mode 100644 index 0000000000..51aa82118e --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideBuilder.java @@ -0,0 +1,46 @@ +package com.fr.design.mainframe.guide.base; + +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.guide.scene.GuideScene; + +public class GuideBuilder { + private Guide guide; + + public static GuideBuilder newInstance() { + return new GuideBuilder(); + } + + public GuideBuilder() { + guide = new Guide(); + guide.setGuideView(new GuideView(DesignerContext.getDesignerFrame(), guide)); + } + + public GuideBuilder setID(String id) { + guide.setId(id); + return this; + } + + public GuideBuilder setName(String name) { + guide.setName(name); + return this; + } + + public GuideBuilder setDescription(String description) { + guide.setDescription(description); + return this; + } + + public GuideBuilder addScene(GuideScene scene) { + guide.setScene(scene); + return this; + } + + public GuideBuilder registerLifecycle(GuideLifecycle lifecycle) { + guide.registerLifecycle(lifecycle); + return this; + } + + public Guide getGuide() { + return guide; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideGroup.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideGroup.java new file mode 100644 index 0000000000..25310fb577 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideGroup.java @@ -0,0 +1,51 @@ +package com.fr.design.mainframe.guide.base; + +import java.util.ArrayList; +import java.util.List; + +public class GuideGroup { + private List guideList; + private String id; + private String name; + + public GuideGroup(String id, String name) { + this.id = id; + this.name = name; + guideList = new ArrayList<>(); + } + + public String getID() { + return id; + } + + public String getName() { + return name; + } + + public void addGuide(Guide guide) { + guideList.add(guide); + } + + public List getGuideList() { + return guideList; + } + + public List getCompleteGuideList() { + List complete = new ArrayList<>(); + for (Guide guide : getGuideList()) { + if (guide.isComplete()) { + complete.add(guide); + } + } + return complete; + } + + public boolean isCompleteAll() { + return getCompleteGuideList().size() == getGuideList().size(); + } + + public boolean isCompleteSome() { + List completeGuides = getCompleteGuideList(); + return completeGuides.size() > 0 && completeGuides.size() < getGuideList().size(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycle.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycle.java new file mode 100644 index 0000000000..2ddfd975f1 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycle.java @@ -0,0 +1,32 @@ +package com.fr.design.mainframe.guide.base; + +public interface GuideLifecycle{ + /** + * 准备环境 + * @return 返回true引导可继续执行 + */ + boolean prepared(); + + /** + * 开始引导教程 + */ + void onStart(); + + /** + * 完成引导教程 + */ + + void onComplete(); + + /** + * 终止引导教程 + */ + + void onTerminate(); + + /** + * 结束引导教程 + */ + void onEnd(); + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycleAdaptor.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycleAdaptor.java new file mode 100644 index 0000000000..c67f47e35f --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideLifecycleAdaptor.java @@ -0,0 +1,28 @@ +package com.fr.design.mainframe.guide.base; + +public abstract class GuideLifecycleAdaptor implements GuideLifecycle{ + @Override + public boolean prepared() { + return true; + } + + @Override + public void onStart() { + + } + + @Override + public void onComplete() { + + } + + @Override + public void onTerminate() { + + } + + @Override + public void onEnd() { + + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideManager.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideManager.java new file mode 100644 index 0000000000..d9c2ac061e --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideManager.java @@ -0,0 +1,88 @@ +package com.fr.design.mainframe.guide.base; + +import com.fr.design.mainframe.guide.collect.GuideCollector; +import com.fr.stable.StringUtils; + +import java.util.ArrayList; +import java.util.List; + +public class GuideManager { + private Guide currentGuide; + private List guideGroupList; + private static GuideManager guideManager; + + public static GuideManager getInstance() { + if (guideManager == null) { + guideManager = new GuideManager(); + } + return guideManager; + } + + public GuideManager() { + guideGroupList = new ArrayList<>(); + } + + public Guide getCurrentGuide() { + return currentGuide; + } + + public void setCurrentGuide(Guide currentGuide) { + this.currentGuide = currentGuide; + } + + public void addGuideGroup(GuideGroup guideGroup) { + this.guideGroupList.add(guideGroup); + } + + public void addGuide(String groupId, Guide guide) { + for (GuideGroup group : guideGroupList) { + if (StringUtils.equals(groupId, group.getID())) { + group.addGuide(guide); + } + } + } + + public List getGuideGroupList() { + return guideGroupList; + } + + public List getAllGuide() { + List guideList = new ArrayList<>(); + for (GuideGroup group : getGuideGroupList()) { + for (Guide guide : group.getGuideList()) { + guideList.add(guide); + } + } + return guideList; + } + + public GuideGroup getGuideGroup(String groupId) { + for (GuideGroup group : getGuideGroupList()) { + if (StringUtils.equals(groupId, group.getID())) { + return group; + } + } + return null; + } + + public Guide getGuide(String guideId) { + for (GuideGroup group : getGuideGroupList()) { + for (Guide guide : group.getGuideList()) { + if (StringUtils.equals(guideId, guide.getID())) { + return guide; + } + } + } + + return null; + } + + public void completeAll() { + for (GuideGroup group : getGuideGroupList()) { + for (Guide guide : group.getGuideList()) { + guide.setComplete(true); + } + } + GuideCollector.getInstance().saveInfo(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideView.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideView.java new file mode 100644 index 0000000000..eb72c7ebd7 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/base/GuideView.java @@ -0,0 +1,132 @@ +package com.fr.design.mainframe.guide.base; + +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.guide.scene.AbstractGuideScene; +import com.fr.design.mainframe.guide.scene.GuideScene; + +import javax.swing.JWindow; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Window; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowEvent; +import java.awt.event.WindowFocusListener; + +public class GuideView extends JWindow{ + private static GuideView guideView; + private Guide invoker; + private GuideScene scene; + private Color modalColor; + private float modalOpacity; + private Window window; + + public static GuideView getInstance(Guide guide) { + if (guideView == null) { + guideView = new GuideView(DesignerContext.getDesignerFrame(), guide); + } + return guideView; + } + + public GuideView(Window window) { + super(window); + this.window = window; + this.modalColor = Color.BLACK; + this.modalOpacity = 0.6f; + this.setPreferredSize(window.getSize()); + this.setSize(window.getSize()); + this.setLayout(FRGUIPaneFactory.createBorderLayout()); + setBg(); + window.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + setLocationRelativeTo(window); + } + + @Override + public void componentMoved(ComponentEvent e) { + setLocation(window.getLocation()); + } + }); + window.addWindowFocusListener(new WindowFocusListener() { + @Override + public void windowGainedFocus(WindowEvent e) { + requestFocus(); + setLocationRelativeTo(window); + } + + @Override + public void windowLostFocus(WindowEvent e) { + + } + }); + this.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (scene == null) { + invoker.terminate(); + } + if (scene instanceof AbstractGuideScene && invoker != null) { + if (((AbstractGuideScene) scene).getHighlightList().size() == 0) { + invoker.terminate(); + } + } + } + }); + + } + + public GuideView(Window window, Guide guide) { + this(DesignerContext.getDesignerFrame()); + this.invoker = guide; + } + + public void setModalColor(Color color) { + modalColor = color; + setBg(); + } + + public void setModalOpacity(float opacity){ + modalOpacity = opacity; + setBg(); + } + + private void setBg() { + Color newColor = new Color(modalColor.getRed(), modalColor.getGreen(), modalColor.getBlue(), (int) (255 * modalOpacity)); + this.setBackground(newColor); + } + + public void setScene(GuideScene scene) { + if (scene instanceof AbstractGuideScene) { + ((AbstractGuideScene) scene).setContainer(this); + } + this.scene = scene; + } + + public void showGuide() { + this.setLocationRelativeTo(window); + this.setSize(window.getSize()); + this.setLocation(window.getLocation()); + this.setVisible(true); + if (scene != null) { + scene.start(); + } else { + GuideManager.getInstance().getCurrentGuide().complete(); + } + } + + public void dismissGuide() { + this.getLayeredPane().removeAll(); + revalidate(); + repaint(); + this.setVisible(false); + } + + @Override + public void paint(Graphics g) { + super.paint(g); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/collect/GuideCollector.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/collect/GuideCollector.java new file mode 100644 index 0000000000..b1d85761ca --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/collect/GuideCollector.java @@ -0,0 +1,155 @@ +package com.fr.design.mainframe.guide.collect; + +import com.fr.base.io.XMLReadHelper; +import com.fr.design.mainframe.guide.base.Guide; +import com.fr.design.mainframe.guide.base.GuideManager; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.ProductConstants; +import com.fr.stable.StableUtils; +import com.fr.stable.xml.XMLPrintWriter; +import com.fr.stable.xml.XMLReadable; +import com.fr.stable.xml.XMLTools; +import com.fr.stable.xml.XMLable; +import com.fr.stable.xml.XMLableReader; +import com.fr.third.javax.xml.stream.XMLStreamException; +import com.fr.third.org.apache.commons.io.FileUtils; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +public class GuideCollector implements XMLable { + private static final String ROOT_XML = "GuideCollector"; + private static final String GUIDE_XML = "Guide"; + private static final String FILE_NAME = "guide.info"; + private static GuideCollector collector; + + private boolean showHint; + private List cacheGuides; + + public static GuideCollector getInstance() { + if (collector == null) { + collector = new GuideCollector(); + } + return collector; + } + + public GuideCollector() { + cacheGuides = new ArrayList<>(); + } + + public boolean isShowHint() { + return showHint; + } + + public void setShowHint(boolean showHint) { + this.showHint = showHint; + saveInfo(); + } + + public void load() { + for(Guide cacheGuide : cacheGuides) { + Guide guide = GuideManager.getInstance().getGuide(cacheGuide.getID()); + if (guide != null) { + guide.setComplete(cacheGuide.isComplete()); + } + } + } + + public void saveInfo() { + cacheGuides = GuideManager.getInstance().getAllGuide(); + try { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + XMLTools.writeOutputStreamXML(this, out); + out.flush(); + out.close(); + String fileContent = new String(out.toByteArray(), StandardCharsets.UTF_8); + FileUtils.writeStringToFile(getInfoFile(), fileContent, StandardCharsets.UTF_8); + } catch (Exception ex) { + FineLoggerFactory.getLogger().error(ex.getMessage()); + } + } + + private File getInfoFile() { + File file = new File(StableUtils.pathJoin(ProductConstants.getEnvHome(), FILE_NAME)); + try { + if (!file.exists()) { + file.createNewFile(); + } + } catch (Exception ex) { + FineLoggerFactory.getLogger().error(ex.getMessage(), ex); + } + return file; + } + + public void loadFromFile() { + if (!getInfoFile().exists()) { + return; + } + XMLableReader reader = null; + try (InputStream in = new FileInputStream(getInfoFile())) { + reader = XMLReadHelper.createXMLableReader(in, XMLPrintWriter.XML_ENCODER); + if (reader == null) { + return; + } + reader.readXMLObject(this); + } catch (FileNotFoundException e) { + } catch (XMLStreamException | IOException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } finally { + try { + if (reader != null) { + reader.close(); + } + } catch (XMLStreamException e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + } + + @Override + public void readXML(XMLableReader xmLableReader) { + String tagName = xmLableReader.getTagName(); + if (tagName.equals(ROOT_XML)) { + showHint = xmLableReader.getAttrAsBoolean("showHint", false); + xmLableReader.readXMLObject(new XMLReadable() { + public void readXML(XMLableReader reader) { + String tagName = reader.getTagName(); + if (tagName.equals(GUIDE_XML)) { + Guide guide = new Guide(); + guide.setId(reader.getAttrAsString("id", null)); + guide.setComplete(reader.getAttrAsBoolean("isComplete", false)); + cacheGuides.add(guide); + } + + } + }); + } + } + + @Override + public void writeXML(XMLPrintWriter writer) { + writer.startTAG(ROOT_XML); + writer.attr("showHint", showHint); + for(Guide guide : GuideManager.getInstance().getAllGuide()) { + writer.startTAG(GUIDE_XML); + writer.attr("id", guide.getID()); + writer.attr("isComplete", guide.isComplete()); + writer.end(); + } + writer.end(); + + + } + + @Override + public Object clone() throws CloneNotSupportedException { + return null; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/AbstractGuideScene.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/AbstractGuideScene.java new file mode 100644 index 0000000000..4247f2ca2d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/AbstractGuideScene.java @@ -0,0 +1,386 @@ +package com.fr.design.mainframe.guide.scene; + +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.mainframe.guide.base.GuideManager; +import com.fr.design.mainframe.guide.base.GuideView; +import com.fr.design.mainframe.guide.utils.ScreenImage; +import com.fr.design.mainframe.guide.tip.BubbleTip; +import com.fr.design.mainframe.guide.tip.GuideTip; +import com.fr.stable.StringUtils; + +import javax.swing.ImageIcon; +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; +import java.awt.AWTException; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.image.BufferedImage; +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractGuideScene extends JPanel implements GuideScene { + private GuideScene nextScene; + private SceneFilter sceneFilter; + private GuideSceneLifecycle lifecycle; + private GuideView container; + private List targetList; + private List highlightList; + private Component nextButton; + + public AbstractGuideScene() { + this.setLayout(null); + this.setOpaque(false); + targetList = new ArrayList<>(); + highlightList = new ArrayList<>(); + } + + /** + * 根据设计器上组件添加高亮区域视图, + * JComponent 采用组件绘制方式 + * Component 采用屏幕截图方式 + * @param component 设计器组件对象 + * @return + */ + public boolean addTarget(Component component) { + try { + if (component == null || container == null) { + return false; + } + + Point point = SwingUtilities.convertPoint(component,0,0, container.getRootPane()); + Rectangle rectangle = new Rectangle(point, component.getSize()); + BufferedImage image; + + if (component instanceof JComponent) { + JComponent jComponent = (JComponent) component; + image = ScreenImage.createImage(jComponent); + } else if (component instanceof Window){ + Window window = (Window) component; + window.toFront(); + window.requestFocus(); + image = ScreenImage.createImage(component); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + container.toFront(); + container.requestFocus(); + } + }); + } else { + image = captureImage(component); + } + targetList.add(component); + highlightList.add(getTargetComponentWithImage(image, rectangle)); + return true; + } catch (AWTException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据设计器上选定区域获取高亮视图,采用截屏的方式 + * @param rectangle 选定区域,相对屏幕 + * @return + */ + public boolean addTarget(Rectangle rectangle) { + try { + targetList.add(null); + container.setVisible(false); + BufferedImage image = captureImage(rectangle); + highlightList.add(getTargetComponentWithImage(image, rectangle)); + return true; + } catch (AWTException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 根据设计器组件,选定其中某个区域,添加高亮视图 + * @param component 设计器组件 + * @param origin 相对组件的区域 + * @return + */ + public boolean addTarget(Component component, Rectangle origin) { + try { + if (component == null) { + return false; + } + Point point = SwingUtilities.convertPoint(component,0,0, container.getRootPane()); + Rectangle rectangle = new Rectangle(point.x + origin.x, point.y + origin.y, origin.width, origin.height); + + BufferedImage image; + + if (component instanceof JComponent) { + JComponent jComponent = (JComponent) component; + image = ScreenImage.createImage(jComponent, origin); + } else { + image = ScreenImage.createImage(component).getSubimage(origin.x, origin.y, origin.width, origin.height); + } + targetList.add(component); + highlightList.add(getTargetComponentWithImage(image, rectangle)); + return true; + } catch (AWTException e) { + e.printStackTrace(); + return false; + } + } + + /** + * 添加自定义组件 + * @param component 自定义组件 + * @param rectangle 相对引导页的区域 + * @return + */ + public boolean addCustomTarget(Component component, Rectangle rectangle) { + if (component == null) { + return false; + } + component.setBounds(rectangle); + targetList.add(component); + highlightList.add(component); + return true; + } + + public boolean addCustomTarget(Component component, Point location) { + return addCustomTarget(component, new Rectangle(location, component.getPreferredSize())); + } + + protected List getTargetList() { + return targetList; + } + + public List getHighlightList() { + return highlightList; + } + + private UILabel getTargetComponentWithImage(BufferedImage image, Rectangle rectangle) { + ImageIcon ic = new ImageIcon(image); + UILabel label = new UILabel(ic); + label.setOpaque(true); + label.setBounds(rectangle); + return label; + } + + private BufferedImage captureImage(Rectangle rectangle) throws AWTException { + container.setVisible(false); + BufferedImage image = ScreenImage.createImage(rectangle); + showContainer(); + return image; + } + + private BufferedImage captureImage(Component component) throws AWTException { + container.setVisible(false); + BufferedImage image = ScreenImage.createImage(component); + showContainer(); + return image; + } + private void showContainer() { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + container.setVisible(true); + container.toFront(); + container.requestFocus(); + } + }); + } + + + /** + * 自定义位置添加气泡提示组件 + * @param title 提示标题 + * @param content 提示内容 + * @param direction 气泡窗口位置方向 + * @param anchor 气泡窗口箭头坐标 + * @param bubbleTailStart 气泡窗口箭头坐标相对于位置方向交叉轴的比例(从左到右,从上到下) + */ + public void addBubbleTip(String title,String content, GuideTip.Direction direction, Point anchor, float bubbleTailStart) { + BubbleTip bt = new BubbleTip(title, content, direction, bubbleTailStart); + bt.setAnchor(anchor); + this.add(bt.getTip()); + } + + /** + * 以上一个添加的引导目标窗口为基础,添加对应的气泡窗 + * @param title 提示标题 + * @param content 提示内容 + * @param direction + * @param anchorStart + * @param bubbleTailStart + */ + public void addBubbleTip(String title, String content, GuideTip.Direction direction, float anchorStart, float bubbleTailStart) { + if (highlightList.size() == 0) { + return; + } + Component lastTarget = highlightList.get(highlightList.size() - 1); + Rectangle bounds = lastTarget.getBounds(); + Point anchor = new Point(0,0); + if (direction == GuideTip.Direction.TOP) { + anchor = new Point(bounds.x + (int)(bounds.width * anchorStart), bounds.y); + } else if (direction == GuideTip.Direction.BOTTOM) { + anchor = new Point(bounds.x + (int)(bounds.width * anchorStart), bounds.y + bounds.height); + } else if (direction == GuideTip.Direction.LEFT) { + anchor = new Point(bounds.x, bounds.y + (int)(bounds.height * anchorStart)); + } else if (direction == GuideTip.Direction.RIGHT) { + anchor = new Point(bounds.x + bounds.width, bounds.y + (int) (bounds.height * anchorStart)); + } + addBubbleTip(title, content, direction, anchor, bubbleTailStart); + } + + public void addBubbleTip(String title, String content, GuideTip.Direction direction) { + addBubbleTip(title, content, direction, 0.5f, 0.5f); + } + + public void addBubbleTip(String title, GuideTip.Direction direction) { + addBubbleTip(title, StringUtils.EMPTY, direction); + } + + public void addTip(GuideTip tip) { + this.add(tip.getTip()); + } + + public void setContainer(GuideView container) { + this.container = container; + } + + public GuideView getContainer() { + return container; + } + + @Override + public GuideScene nextScene(GuideScene scene) { + nextScene = scene; + return nextScene; + } + + @Override + public void addSceneFilter(SceneFilter filter) { + sceneFilter = filter; + } + + @Override + public void start() { + clear(); + if (lifecycle != null && !lifecycle.prepared()) { + return; + } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + showScene(); + } + }); + } + + @Override + public void showScene() { + if (container != null) { + container.setContentPane(this); + this.setBounds(0, 0 , getSceneWidth(), getSceneWidth()); + + // show target + for (int index = highlightList.size() - 1; index >= 0; index--) { + this.add(highlightList.get(index)); + } + // show next button + if (nextButton != null) { + nextButton.setBounds((getSceneWidth() - 60) / 2, getSceneHeight() - 100, 60, 30); + this.add(nextButton); + } + + container.revalidate(); + container.repaint(); + } + if (lifecycle != null) { + lifecycle.onShow(); + } + } + + @Override + public void complete() { + container.getLayeredPane().remove(this); + container.repaint(); + + if (lifecycle != null) { + lifecycle.onComplete(); + } + if (sceneFilter != null) { + nextScene = sceneFilter.getFilterScene(); + } + if (nextScene != null) { + if (nextScene instanceof AbstractGuideScene) { + ((AbstractGuideScene) nextScene).setContainer(container); + } + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + nextScene.start(); + } + }); + } else { + GuideManager.getInstance().getCurrentGuide().complete(); + } + } + + @Override + public void registerLifecycle(GuideSceneLifecycle lifecycle) { + this.lifecycle = lifecycle; + } + + @Override + public void removeLifecycle() { + this.lifecycle = null; + } + + public void showNextButton() { + UIButton nextButton = new UIButton("Next"); + nextButton.setPreferredSize(new Dimension(60, 30)); + nextButton.setOpaque(false); + nextButton.setFont(nextButton.getFont().deriveFont(20)); + nextButton.setRoundBorder(true, 8); + nextButton.setForeground(Color.WHITE); + nextButton.setNormalPainted(false); + nextButton.setPressedPainted(false); + nextButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + complete(); + } + }); + this.nextButton = nextButton; + } + + private int getSceneWidth() { + if (container == null) { + return 0; + } + return container.getLayeredPane().getWidth(); + + } + + private int getSceneHeight() { + if (container == null) { + return 0; + } + return container.getLayeredPane().getHeight(); + } + + private void clear() { + targetList = new ArrayList<>(); + highlightList = new ArrayList<>(); + this.nextButton = null; + this.removeAll(); + invalidate(); + repaint(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/ClickScene.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/ClickScene.java new file mode 100644 index 0000000000..6b0f4b0d5b --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/ClickScene.java @@ -0,0 +1,90 @@ +package com.fr.design.mainframe.guide.scene; + +import java.awt.Component; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class ClickScene extends AbstractGuideScene{ + public enum ClickType { + LEFT, LEFT_DOUBLE, RIGHT + } + + public void addClickTarget(Component component, ClickType clickType) { + addClickTarget(component, clickType, false); + } + + public void addClickTarget(Component component, ClickType clickType, boolean isDispatch) { + if (super.addTarget(component)) { + addTargetClickListener(clickType, isDispatch); + } + } + + public void addClickTarget(Rectangle rectangle, ClickType clickType) { + if (super.addTarget(rectangle)) { + addTargetClickListener(clickType,false); + } + } + + public void addClickTarget(Component component, Rectangle rectangle, ClickType clickType) { + if (super.addTarget(component, rectangle)) { + addTargetClickListener(clickType, false); + } + } + + public void addCustomClickTarget(Component component, Rectangle rectangle, ClickType clickType) { + if (super.addCustomTarget(component, rectangle)) { + addTargetClickListener(clickType, false); + } + } + + public void addCustomClickTarget(Component component, Point location, ClickType clickType) { + if (super.addCustomTarget(component, location)) { + addTargetClickListener(clickType, false); + } + } + + private void addTargetClickListener(ClickType clickType, boolean isDispatch) { + Component highlight = getHighlightList().get(getHighlightList().size() - 1); + Component target = getTargetList().get(getTargetList().size() - 1); + highlight.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getButton() == MouseEvent.BUTTON1) { + if ((e.getClickCount() == 1 && clickType == ClickType.LEFT) || (e.getClickCount() == 2 && clickType == ClickType.LEFT_DOUBLE)) { + if (isDispatch) { + redispatchMouseEvent(e, target); + } + complete(); + } + } else if (e.getButton() == MouseEvent.BUTTON3 && clickType == ClickType.RIGHT) { + if (isDispatch) { + redispatchMouseEvent(e, target); + } + complete(); + } + } + + @Override + public void mousePressed(MouseEvent e) { + if (isDispatch) { + redispatchMouseEvent(e, target); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + if (isDispatch) { + redispatchMouseEvent(e, target); + } + } + }); + } + + private void redispatchMouseEvent(MouseEvent e, Component component) { + component.dispatchEvent(new MouseEvent(component, e.getID(), e + .getWhen(), e.getModifiers(), e.getX(), + e.getY(), e.getClickCount(), e.isPopupTrigger())); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DisplayScene.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DisplayScene.java new file mode 100644 index 0000000000..1c44012d17 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DisplayScene.java @@ -0,0 +1,33 @@ +package com.fr.design.mainframe.guide.scene; + +import java.util.Timer; +import java.util.TimerTask; + +public class DisplayScene extends AbstractGuideScene { + private long delay; + private static final long DEFAULT_DELAY = 1000; + + public DisplayScene() { + this(DEFAULT_DELAY); + } + + public DisplayScene(long delay) { + super(); + this.delay = delay; + } + + public void setDelay(long delay) { + this.delay = delay; + } + + @Override + public void showScene() { + super.showScene(); + // 实例化Timer类 + new Timer().schedule(new TimerTask() { + public void run() { + complete(); + } + }, delay); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DragScene.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DragScene.java new file mode 100644 index 0000000000..c6a36d2ef0 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/DragScene.java @@ -0,0 +1,63 @@ +package com.fr.design.mainframe.guide.scene; + +import com.fr.design.mainframe.guide.scene.drag.DragAndDropDragGestureListener; + +import java.awt.Component; +import java.awt.Rectangle; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DropTarget; +import java.awt.dnd.DropTargetDropEvent; + +public class DragScene extends AbstractGuideScene{ + public enum DragType{ + NONE, FROM, TO + } + + public void addDragTarget(Component component, DragType type) { + if (super.addTarget(component)) { + addDragTargetListener(type); + } + } + + public void addDragTarget(Rectangle rectangle, DragType type) { + if (super.addTarget(rectangle)) { + addDragTargetListener(type); + } + } + + public void addDragTarget(Component component, Rectangle rectangle, DragType type) { + if (super.addTarget(component, rectangle)) { + addDragTargetListener(type); + } + } + + public void addCustomDragTarget(Component component, Rectangle rectangle, DragType type) { + if (super.addCustomTarget(component, rectangle)) { + addDragTargetListener(type); + } + } + + private void addDragTargetListener(DragType dragType) { + Component target = getHighlightList().get(getHighlightList().size() - 1); + + if (dragType == DragType.FROM) { + new DragAndDropDragGestureListener(target, DnDConstants.ACTION_COPY_OR_MOVE){ + @Override + public void dragDropEnd(DragSourceDropEvent dsde) { + complete(); + } + }; + } else if (dragType == DragType.TO) { + target.setDropTarget(new DropTarget()); + } + } + + private class DropSceneTarget extends DropTarget { + @Override + public synchronized void drop(DropTargetDropEvent dtde) { + super.drop(dtde); + complete(); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideScene.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideScene.java new file mode 100644 index 0000000000..e62ca4d9a9 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideScene.java @@ -0,0 +1,18 @@ +package com.fr.design.mainframe.guide.scene; + + +public interface GuideScene { + GuideScene nextScene(GuideScene scene); + + void addSceneFilter(SceneFilter filter); + + void start(); + + void showScene(); + + void complete(); + + void registerLifecycle(GuideSceneLifecycle lifecycle); + + void removeLifecycle(); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycle.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycle.java new file mode 100644 index 0000000000..9b9e072780 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycle.java @@ -0,0 +1,20 @@ +package com.fr.design.mainframe.guide.scene; + +public interface GuideSceneLifecycle { + /** + * 引导场景准备工作 + * 给 scene 添加 target 应该在这个阶段处理 + * @return + */ + boolean prepared(); + + /** + * scene 显示后 + */ + void onShow(); + + /** + * scene 完成后 + */ + void onComplete(); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycleAdaptor.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycleAdaptor.java new file mode 100644 index 0000000000..06c40ad11f --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/GuideSceneLifecycleAdaptor.java @@ -0,0 +1,18 @@ +package com.fr.design.mainframe.guide.scene; + +public abstract class GuideSceneLifecycleAdaptor implements GuideSceneLifecycle { + @Override + public boolean prepared() { + return true; + } + + @Override + public void onShow() { + + } + + @Override + public void onComplete() { + + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/SceneFilter.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/SceneFilter.java new file mode 100644 index 0000000000..ea4ccf28fa --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/SceneFilter.java @@ -0,0 +1,5 @@ +package com.fr.design.mainframe.guide.scene; + +public interface SceneFilter { + GuideScene getFilterScene(); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/drag/DragAndDropDragGestureListener.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/drag/DragAndDropDragGestureListener.java new file mode 100644 index 0000000000..25979925d4 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/scene/drag/DragAndDropDragGestureListener.java @@ -0,0 +1,60 @@ +package com.fr.design.mainframe.guide.scene.drag; + +import com.fr.general.ComparatorUtils; +import org.jetbrains.annotations.NotNull; + +import java.awt.Component; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceAdapter; + +public class DragAndDropDragGestureListener extends DragSourceAdapter implements DragGestureListener { + private Component component; + + public DragAndDropDragGestureListener(Component component, int actions) { + this.component = component; + DragSource source = new DragSource(); + source.createDefaultDragGestureRecognizer(component, actions, this); + } + + public void dragGestureRecognized(DragGestureEvent dge) { + if (component != null) { + try { + DragAndDropTransferable dragAndDropTransferable = new DragAndDropTransferable(component); + dge.startDrag(DragSource.DefaultCopyDrop, dragAndDropTransferable, this); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private static class DragAndDropTransferable implements Transferable { + private Component component; + + public DragAndDropTransferable(Component component) { + this.component = component; + } + + DataFlavor[] flavors = {new DataFlavor(Component.class, "Component")}; + + public DataFlavor[] getTransferDataFlavors() { + return flavors; + } + + public boolean isDataFlavorSupported(DataFlavor flavor) { + for (DataFlavor df : flavors) { + if (ComparatorUtils.equals(df, flavor)) { + return true; + } + } + return false; + } + @NotNull + public Object getTransferData(DataFlavor df) { + return component; + } + } +} \ No newline at end of file diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/BubbleTip.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/BubbleTip.java new file mode 100644 index 0000000000..7d954150ff --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/BubbleTip.java @@ -0,0 +1,83 @@ +package com.fr.design.mainframe.guide.tip; + +import com.fr.design.mainframe.guide.base.Guide; +import com.fr.design.mainframe.guide.base.GuideManager; +import com.fr.design.mainframe.guide.ui.bubble.Bubble; +import com.fr.design.mainframe.guide.ui.bubble.BubbleWithClose; + +import javax.swing.JComponent; +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class BubbleTip implements GuideTip { + private BubbleWithClose bubbleBox; + private Point anchor; + + /** + * + * @param title 标题 + * @param content 内容 + * @param direction 气泡提示相对anchor的方向,这里方向会和气泡组件箭头方向相反 + * @param tailStart 气泡尾巴相对当前边垂直方向偏移位置比例,值在0-1范围 + */ + public BubbleTip(String title, String content, Direction direction, float tailStart) { + bubbleBox = new BubbleWithClose(title, content, getBubbleBoxTailDirection(direction), tailStart); + bubbleBox.addCloseActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + Guide currentGuide = GuideManager.getInstance().getCurrentGuide(); + if (currentGuide != null) { + currentGuide.terminate(); + } + } + }); + } + @Override + public JComponent getTip() { + if(anchor == null) { + return new BubbleWithClose(bubbleBox.getTitle(), bubbleBox.getContent(), Bubble.TailDirection.TOP, 0); + } else { + setBubbleBoxBound(); + return bubbleBox; + } + } + + /** + * 设置锚点坐标 + * 这里可以指定气泡组件箭头坐标的箭头坐标 + * @param anchor + */ + public void setAnchor(Point anchor) { + this.anchor = anchor; + } + + private void setBubbleBoxBound() { + int bubbleW = bubbleBox.getPreferredSize().width; + int bubbleH = bubbleBox.getPreferredSize().height; + if (bubbleBox.isTailHorizontal()) { + int x = bubbleBox.isTailLeft() ? anchor.x : anchor.x - bubbleW; + int y = anchor.y - (int) (bubbleH * bubbleBox.getTailStart()); + bubbleBox.setBounds(x, y, bubbleW, bubbleH); + } else if (bubbleBox.isTailVertical()) { + int x = anchor.x - (int) (bubbleW * bubbleBox.getTailStart()); + int y = bubbleBox.isTailTop() ? anchor.y : anchor.y - bubbleH; + bubbleBox.setBounds(x, y, bubbleW, bubbleH); + } + } + + private Bubble.TailDirection getBubbleBoxTailDirection(Direction direction) { + switch (direction) { + case TOP: + return Bubble.TailDirection.BOTTOM; + case BOTTOM: + return Bubble.TailDirection.TOP; + case LEFT: + return Bubble.TailDirection.RIGHT; + case RIGHT: + default: + return Bubble.TailDirection.LEFT; + } + } + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/GuideTip.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/GuideTip.java new file mode 100644 index 0000000000..e1573601f8 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/tip/GuideTip.java @@ -0,0 +1,14 @@ +package com.fr.design.mainframe.guide.tip; + +import javax.swing.JComponent; +import java.awt.Point; + +public interface GuideTip { + enum Direction { + TOP, BOTTOM, LEFT, RIGHT + } + + JComponent getTip(); + + void setAnchor(Point anchor); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHint.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHint.java new file mode 100644 index 0000000000..56f978731b --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHint.java @@ -0,0 +1,74 @@ +package com.fr.design.mainframe.guide.ui; + +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.layout.VerticalFlowLayout; +import com.fr.design.mainframe.guide.ui.bubble.Bubble; + +import javax.swing.BorderFactory; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.event.ActionListener; + +public class BubbleHint extends Bubble { + private UIButton confirmButton; + + public BubbleHint() { + super(TailDirection.TOP, 0.9f); + initComponent(); + } + + private void initComponent() { + this.setLayout(new BorderLayout()); + this.setBorder(BorderFactory.createEmptyBorder(10 + Bubble.TAIL_HEIGHT, 15, 10, 15)); + this.setPreferredSize(new Dimension(220, 140)); + + JPanel contentPane = FRGUIPaneFactory.createVerticalFlowLayout_Pane(false, VerticalFlowLayout.CENTER, 0, 0); + contentPane.setOpaque(false); + + UILabel title = new UILabel(Toolkit.i18nText("Fine-Design_Guide_Tips_Title")); + title.setFont(title.getFont().deriveFont(16.0f)); + title.setForeground(new Color(62, 155, 249)); + title.setPreferredSize(new Dimension(190, 30)); + title.setHorizontalAlignment(SwingConstants.CENTER); + title.setBorder(BorderFactory.createEmptyBorder(0,0,5,0)); + + UILabel content1 = new UILabel(Toolkit.i18nText("Fine-Design_Guide_Tips_Content1")); + content1.setPreferredSize(new Dimension(190,20)); + content1.setHorizontalAlignment(SwingConstants.CENTER); + + UILabel content2 = new UILabel(Toolkit.i18nText("Fine-Design_Guide_Tips_Content2")); + content2.setPreferredSize(new Dimension(190,20)); + content2.setHorizontalAlignment(SwingConstants.CENTER); + + JPanel buttonContainer= FRGUIPaneFactory.createCenterFlowZeroGapBorderPane(); + buttonContainer.setBorder(BorderFactory.createEmptyBorder(20, 0, 0, 0)); + buttonContainer.setPreferredSize(new Dimension(190,40)); + buttonContainer.setOpaque(false); + + confirmButton = new UIButton(Toolkit.i18nText("Fine-Design_Guide_Tips_Know")); + confirmButton.setPreferredSize(new Dimension(78, 24)); + buttonContainer.add(confirmButton); + + contentPane.add(title); + contentPane.add(content1); + contentPane.add(content2); + contentPane.add(buttonContainer); + + this.add(contentPane, BorderLayout.CENTER); + } + + public void addConfirmAction(ActionListener listener) { + confirmButton.addActionListener(listener); + } + + public void removeConfirmAction(ActionListener listener) { + confirmButton.removeActionListener(listener); + } + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHintDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHintDialog.java new file mode 100644 index 0000000000..6e67114af6 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/BubbleHintDialog.java @@ -0,0 +1,30 @@ +package com.fr.design.mainframe.guide.ui; + +import com.fr.design.mainframe.guide.collect.GuideCollector; + +import javax.swing.JDialog; +import java.awt.Color; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class BubbleHintDialog extends JDialog { + private static final int DIALOG_WIDTH = 220; + private static final int DIALOG_HEIGHT = 140; + public BubbleHintDialog(Window parent) { + super(parent); + setUndecorated(true); + this.setBackground(new Color(0,0,0,0)); + setSize(DIALOG_WIDTH, DIALOG_HEIGHT); + BubbleHint bubbleHint = new BubbleHint(); + bubbleHint.addConfirmAction(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setVisible(false); + dispose(); + GuideCollector.getInstance().setShowHint(true); + } + }); + this.setContentPane(bubbleHint); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/ExpandPane.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/ExpandPane.java new file mode 100644 index 0000000000..c71a15ea7d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/ExpandPane.java @@ -0,0 +1,78 @@ +package com.fr.design.mainframe.guide.ui; + +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.layout.VerticalFlowLayout; +import com.fr.general.IOUtils; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class ExpandPane extends JPanel { + private static final Icon downIcon = IOUtils.readIcon("/com/fr/design/mainframe/guide/arrow_down.png"); + private static final Icon rightIcon = IOUtils.readIcon("/com/fr/design/mainframe/guide/arrow_right.png"); + private String title; + private boolean expand; + private UILabel arrow; + private JPanel headerPane; + private JPanel contentPane; + + public ExpandPane(String title, JPanel contentPane) { + this.title = title; + this.expand = true; + this.contentPane = contentPane; + initComponent(); + } + + private void initComponent() { + VerticalFlowLayout layout = new VerticalFlowLayout(VerticalFlowLayout.TOP, 10, 5); + layout.setAlignLeft(true); + this.setLayout(layout); + + JPanel headerPane = createHeader(); + this.add(headerPane); + this.add(contentPane); + headerPane.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + setExpand(!expand); + } + }); + } + + private JPanel createHeader() { + headerPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); + headerPane.setPreferredSize(new Dimension(200, 24)); + UILabel headerTitle = new UILabel(this.title); + headerTitle.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0)); + arrow = new UILabel(downIcon); + arrow.setOpaque(false); + arrow.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + headerPane.add(headerTitle, BorderLayout.CENTER); + headerPane.add(arrow, BorderLayout.WEST); + return headerPane; + } + + @Override + public void setPreferredSize(Dimension preferredSize) { + super.setPreferredSize(preferredSize); + headerPane.setPreferredSize(new Dimension(preferredSize.width, headerPane.getPreferredSize().height)); + contentPane.setPreferredSize(new Dimension(preferredSize.width, contentPane.getPreferredSize().height)); + } + + public void setExpand(boolean isExpand) { + this.expand = isExpand; + arrow.setIcon(isExpand ? downIcon : rightIcon); + contentPane.setVisible(isExpand); + } + + public boolean isExpand() { + return expand; + } +} + diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideCompleteDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideCompleteDialog.java new file mode 100644 index 0000000000..8d529f33b8 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideCompleteDialog.java @@ -0,0 +1,117 @@ +package com.fr.design.mainframe.guide.ui; + +import com.fr.design.gui.frpane.UITextPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.mainframe.DesignerContext; +import com.fr.general.IOUtils; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import javax.swing.text.SimpleAttributeSet; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyledDocument; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class GuideCompleteDialog extends JDialog { + private static final int DIALOG_WIDTH = 340; + private static final int DIALOG_HEIGHT = 367; + private static final Icon SUCCESS_ICON = IOUtils.readIcon("/com/fr/design/mainframe/guide/success.png"); + private static final int ICON_HEIGHT = 182; + private static final Color FONT_COLOR = new Color(51, 51, 52); + private static final Color BUTTON_BG_COLOR = new Color(65, 155,249); + private static GuideCompleteDialog dialog; + + public static GuideCompleteDialog getInstance() { + if (dialog == null) { + dialog = new GuideCompleteDialog(DesignerContext.getDesignerFrame()); + } + return dialog; + } + + private UITextPane textArea; + + public GuideCompleteDialog(Window window) { + super(window); + setSize(DIALOG_WIDTH, DIALOG_HEIGHT); + setUndecorated(true); + setLocationRelativeTo(window); + this.setContentPane(getContentPane()); + } + + @Override + public Container getContentPane() { + JPanel contentPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); + + UILabel completeImage = new UILabel(SUCCESS_ICON); + completeImage.setPreferredSize(new Dimension(DIALOG_WIDTH, ICON_HEIGHT)); + + JPanel container = new JPanel(new BorderLayout(0, 10)); + container.setBorder(BorderFactory.createEmptyBorder(0, 52, 25, 52)); + + UILabel title = new UILabel(Toolkit.i18nText("Fine-Design_Guide_Complete_Confirm")); + title.setFont(title.getFont().deriveFont(22.0f)); + title.setPreferredSize(new Dimension(190, 30)); + title.setHorizontalAlignment(SwingConstants.CENTER); + title.setForeground(FONT_COLOR); + + textArea = new UITextPane(); + textArea.setEnabled(false); + textArea.setOpaque(false); + textArea.setFont(textArea.getFont().deriveFont(18.0f)); + textArea.setPreferredSize(new Dimension(236, 52)); + textArea.setBorder(null); + textArea.setDisabledTextColor(FONT_COLOR); + StyledDocument doc = textArea.getStyledDocument(); + SimpleAttributeSet center = new SimpleAttributeSet(); + StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER); + doc.setParagraphAttributes(0, doc.getLength(), center, false); + + JPanel buttonContainer= FRGUIPaneFactory.createCenterFlowZeroGapBorderPane(); + buttonContainer.setPreferredSize(new Dimension(190,38)); + buttonContainer.setOpaque(false); + + JButton button = new JButton(Toolkit.i18nText("Fine-Design_Guide_Complete_End")); + button.setPreferredSize(new Dimension(122, 38)); + button.setBackground(BUTTON_BG_COLOR); + button.setForeground(Color.WHITE); + button.setBorder(null); + button.setContentAreaFilled(false); + button.setOpaque(true); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setVisible(false); + dispose(); + GuideManageDialog.getInstance().showDialog(); + } + }); + + buttonContainer.add(button); + container.add(title, BorderLayout.NORTH); + container.add(textArea, BorderLayout.CENTER); + container.add(buttonContainer,BorderLayout.SOUTH); + + contentPane.add(completeImage, BorderLayout.NORTH); + contentPane.add(container, BorderLayout.CENTER); + + return contentPane; + } + + public void showDialog(String str) { + textArea.setText(Toolkit.i18nText("Fine-Design_Guide_Complete_Hint", str)); + repaint(); + this.setVisible(true); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideManageDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideManageDialog.java new file mode 100644 index 0000000000..3f25d13084 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/GuideManageDialog.java @@ -0,0 +1,226 @@ +package com.fr.design.mainframe.guide.ui; + + +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.icontainer.UIScrollPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.layout.VerticalFlowLayout; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.guide.base.Guide; +import com.fr.design.mainframe.guide.base.GuideGroup; +import com.fr.design.mainframe.guide.base.GuideManager; +import com.fr.design.mainframe.guide.collect.GuideCollector; +import com.fr.design.utils.gui.GUICoreUtils; +import com.fr.general.IOUtils; + +import javax.swing.BorderFactory; +import javax.swing.Icon; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.border.Border; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Window; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +public class GuideManageDialog extends JDialog { + private static final int DEFAULT_HEIGHT = 400; + private static final int DEFAULT_WIDTH = 600; + private static final Icon GROUP_COMPLETE_NONE = IOUtils.readIcon("/com/fr/design/mainframe/guide/complete_none.png"); + private static final Icon GROUP_COMPLETE_SOME = IOUtils.readIcon("/com/fr/design/mainframe/guide/complete_some.png"); + private static final Icon GROUP_COMPLETE_ALL = IOUtils.readIcon("/com/fr/design/mainframe/guide/complete_all.png"); + private static final Color BORDER_COLOR = new Color(224, 224, 225); + private static final Color UNCOMPLETE_FONT_COLOR = new Color(51, 51, 52); + private static final Color COMPLETE_FONT_COLOR = new Color(51,51,52,128); + private static GuideManageDialog dialog; + + private JPanel scrollContent; + + public static GuideManageDialog getInstance() { + if (dialog == null) { + dialog = new GuideManageDialog(DesignerContext.getDesignerFrame()); + } + return dialog; + } + + public GuideManageDialog(Window parent) { + super(parent); + GuideCollector.getInstance().load(); + initComponent(); + } + + private void initComponent() { + this.setTitle(Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Title")); + setResizable(false); + setLayout(FRGUIPaneFactory.createBorderLayout()); + setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); + GUICoreUtils.centerWindow(this); + JPanel contentPane = FRGUIPaneFactory.createBorderLayout_S_Pane(); + contentPane.add(createContentPanel(), BorderLayout.CENTER); + contentPane.add(createActionsPane(), BorderLayout.SOUTH); + setContentPane(contentPane); + } + + private UIScrollPane createContentPanel() { + scrollContent = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.TOP, 0, 5); + UIScrollPane scrollPane = new UIScrollPane(scrollContent); + return scrollPane; + } + + + + private JPanel createGuideGroupCard(GuideGroup guideGroup) { + JPanel card = FRGUIPaneFactory.createBorderLayout_S_Pane(); + card.setBorder(BorderFactory.createLineBorder(BORDER_COLOR)); + card.setBackground(Color.WHITE); + + JPanel cardContainer = FRGUIPaneFactory.createBorderLayout_S_Pane(); + cardContainer.setOpaque(false); + cardContainer.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10)); + card.add(cardContainer, BorderLayout.CENTER); + + cardContainer.add(createGroupTitlePane(guideGroup), BorderLayout.NORTH); + cardContainer.add(createGroupContentPane(guideGroup), BorderLayout.CENTER); + return card; + } + + private JPanel createGroupTitlePane(GuideGroup guideGroup) { + JPanel titleContainer = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.CENTER, 0, 0); + titleContainer.setBorder(getBottomBorder()); + titleContainer.setOpaque(false); + + titleContainer.setPreferredSize(new Dimension(500, 40)); + JPanel titlePane = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(0, 8,0 ); + titlePane.setOpaque(false); + + UILabel iconLabel = new UILabel(getGroupStateIcon(guideGroup)); + iconLabel.setPreferredSize(new Dimension(16, 16)); + iconLabel.setOpaque(false); + + UILabel titleLabel = new UILabel(guideGroup.getName()); + titleLabel.setPreferredSize(new Dimension(200, 16)); + titleLabel.setForeground(new Color(65, 155, 249)); + titleLabel.setOpaque(false); + + titlePane.add(iconLabel); + titlePane.add(titleLabel); + titleContainer.add(titlePane); + return titleContainer; + } + + private JPanel createGroupContentPane(GuideGroup guideGroup) { + JPanel groupContainer = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.TOP, 0, 0); + groupContainer.setBorder(BorderFactory.createEmptyBorder(0,10,0,0)); + groupContainer.setOpaque(false); + for (int index = 0; index < guideGroup.getGuideList().size(); index++) { + JPanel guidePane = createGuidePane(guideGroup.getGuideList().get(index), index); + if (index != guideGroup.getGuideList().size() - 1) { + guidePane.setBorder(getBottomBorder()); + } + groupContainer.add(guidePane); + } + return groupContainer; + } + + + + private JPanel createGuidePane(Guide guide, int index) { + JPanel guidePaneContainer = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.CENTER, 0, 0); + guidePaneContainer.setPreferredSize(new Dimension(540, 40)); + guidePaneContainer.setOpaque(false); + + JPanel guidePane = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(0, 50,0 ); + guidePane.setOpaque(false); + + UILabel title = new UILabel((index + 1) + "、" + guide.getDescription()); + title.setPreferredSize(new Dimension(440, 20)); + title.setForeground(guide.isComplete() ? COMPLETE_FONT_COLOR : UNCOMPLETE_FONT_COLOR); + title.setOpaque(false); + + + UIButton button = new UIButton(guide.isComplete() ? Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Retry") : Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Show")); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setVisible(false); + guide.go(); + } + }); + + button.setPreferredSize(new Dimension(48, 20)); + guidePane.add(title); + guidePane.add(button); + + guidePaneContainer.add(guidePane); + return guidePaneContainer; + } + + private Border getBottomBorder() { + return BorderFactory.createMatteBorder( + 0,0,1, 0, BORDER_COLOR + ); + } + + public void showDialog() { + resetScrollContent(); + this.setVisible(true); + } + + private Icon getGroupStateIcon(GuideGroup guideGroup) { + if (guideGroup.isCompleteAll()) { + return GROUP_COMPLETE_ALL; + } else if (guideGroup.isCompleteSome()) { + return GROUP_COMPLETE_SOME; + } else { + return GROUP_COMPLETE_NONE; + } + } + + private UIButton createCompleteAllButton() { + UIButton button = new UIButton(Toolkit.i18nText(Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Complete_All"))); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + GuideManager.getInstance().completeAll(); + resetScrollContent(); + } + }); + return button; + } + + private UIButton createCloseButton() { + UIButton button = new UIButton(Toolkit.i18nText(Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Close"))); + button.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + }); + return button; + } + + private JPanel createActionsPane() { + JPanel container = FRGUIPaneFactory.createBorderLayout_S_Pane(); + container.setBorder(BorderFactory.createEmptyBorder(7, 10, 7, 10)); + container.add(createCompleteAllButton(), BorderLayout.WEST); + container.add(createCloseButton(), BorderLayout.EAST); + return container; + } + + private void resetScrollContent() { + scrollContent.removeAll(); + JPanel expandContent = FRGUIPaneFactory.createVerticalFlowLayout_Pane(true, VerticalFlowLayout.TOP, 0, 5); + for (GuideGroup guideGroup : GuideManager.getInstance().getGuideGroupList()) { + JPanel guideGroupCard = createGuideGroupCard(guideGroup); + expandContent.add(guideGroupCard); + } + ExpandPane expandPane = new ExpandPane(Toolkit.i18nText("Fine-Design_Guide_Manager_Dialog_Version_Title", "11.0.0"), expandContent); + scrollContent.add(expandPane); + revalidate(); + repaint(); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/Bubble.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/Bubble.java new file mode 100644 index 0000000000..10b04362e1 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/Bubble.java @@ -0,0 +1,152 @@ +package com.fr.design.mainframe.guide.ui.bubble; + +import javax.swing.JComponent; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Composite; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; + +public class Bubble extends JComponent { + public enum TailDirection { + TOP, BOTTOM, LEFT, RIGHT + } + protected static final int TAIL_HEIGHT = 7; + protected static final int TAIL_WIDTH = 14; + protected static final int BUBBLE_WIDTH = 170; + protected static final Color BG_COLOR = new Color(240,240,241); + protected static final int BORDER_RADIUS = 10; + + + private TailDirection tailDirection; + private float tailStart; + + public Bubble() { + this(TailDirection.LEFT, 0.5f); + } + + public Bubble(TailDirection tailDirection, float tailStart) { + this.tailDirection = tailDirection; + this.tailStart = tailStart; + this.setOpaque(false); + } + + public void setTailDirection(TailDirection tailDirection) { + this.tailDirection = tailDirection; + } + + public TailDirection getTailDirection() { + return tailDirection; + } + + public void setTailStart(float tailStart) { + this.tailStart = tailStart; + } + + public float getTailStart() { + return tailStart; + } + + + @Override + public Dimension getPreferredSize() { + if (isPreferredSizeSet()) { + return super.getPreferredSize(); + } + return new Dimension(getDefaultWidth(), getDefaultHeight()); + + } + + protected int getDefaultWidth() { + return (isTailHorizontal() ? TAIL_HEIGHT : 0) + BUBBLE_WIDTH; + } + + protected int getDefaultHeight() { + return (isTailVertical() ? TAIL_HEIGHT : 0); + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g; + Composite oldComposite = g2.getComposite(); + paintBubbleBg(g2); + g2.setComposite(oldComposite); + super.paintComponent(g); + } + + protected void paintBubbleBg(Graphics2D g2) { + g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER)); + paintRectBg(g2); + paintTailBg(g2); + } + + protected void paintRectBg(Graphics2D g2) { + g2.setColor(BG_COLOR); + Rectangle bounds = getRectBounds(); + g2.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, BORDER_RADIUS, BORDER_RADIUS); + } + + protected void paintTailBg(Graphics2D g2) { + int width = getWidth(); + int height = getHeight(); + if (isTailVertical()) { + int tailX = (int) (width * tailStart); + int startX = Math.max(tailX - TAIL_WIDTH / 2, 0); + int endX = startX + TAIL_WIDTH; + int[] xPoints = {startX, endX, tailX}; + int[] yPoints = isTailTop() ? new int[]{TAIL_HEIGHT, TAIL_HEIGHT, 0} : new int[]{height - TAIL_HEIGHT, height - TAIL_HEIGHT, height}; + g2.fillPolygon(xPoints, yPoints, 3); + } else if (isTailHorizontal()) { + int tailY = (int) (height * tailStart); + int startY = Math.max(tailY - TAIL_WIDTH / 2, 0); + int endY = startY + TAIL_WIDTH; + int[] xPoints = isTailLeft() ? new int[]{TAIL_HEIGHT, TAIL_HEIGHT, 0} : new int[]{width - TAIL_HEIGHT, width - TAIL_HEIGHT, width}; + int[] yPoints = {startY, endY, tailY}; + g2.fillPolygon(xPoints, yPoints, 3); + } + } + + public Rectangle getRectBounds() { + int width = getWidth(); + int height = getHeight(); + switch (tailDirection) { + case TOP: + return new Rectangle(0, TAIL_HEIGHT, width, height - TAIL_HEIGHT); + case LEFT: + return new Rectangle(TAIL_HEIGHT, 0, width - TAIL_HEIGHT, height); + case RIGHT: + return new Rectangle(0, 0 , width - TAIL_HEIGHT, height); + case BOTTOM: + return new Rectangle(0, 0, width, height - TAIL_HEIGHT); + default: + return new Rectangle(0,0,0,0); + } + } + + public boolean isTailHorizontal() { + return isTailLeft() || isTailRight(); + } + + public boolean isTailVertical() { + return isTailTop() || isTailBottom(); + } + + public boolean isTailLeft() { + return tailDirection == TailDirection.LEFT; + } + + public boolean isTailRight() { + return tailDirection == TailDirection.RIGHT; + } + + public boolean isTailTop() { + return tailDirection == TailDirection.TOP; + } + + public boolean isTailBottom() { + return tailDirection == TailDirection.BOTTOM; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/BubbleWithClose.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/BubbleWithClose.java new file mode 100644 index 0000000000..b5e966063d --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/ui/bubble/BubbleWithClose.java @@ -0,0 +1,255 @@ +package com.fr.design.mainframe.guide.ui.bubble; + + +import com.fr.base.GraphHelper; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.gui.itextarea.UITextArea; +import com.fr.general.IOUtils; +import com.fr.stable.StringUtils; + +import javax.swing.JTextArea; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.LayoutManager; +import java.awt.Rectangle; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.font.FontRenderContext; +import java.awt.font.LineBreakMeasurer; +import java.awt.font.TextAttribute; +import java.text.AttributedCharacterIterator; +import java.text.AttributedString; + +public class BubbleWithClose extends Bubble { + private static final Font FONT = new Font(Font.SANS_SERIF, Font.PLAIN, 14); + private static final int HEADER_HEIGHT = 24; + private static final Color HEADER_COLOR = new Color(245, 245, 246); + private static final Color TITLE_COLOR = new Color(51, 51, 52); + private static final Color CONTENT_COLOR = new Color(51,51,52,128); + private static final Insets DEFAULT_INSET = new Insets(10, 15, 10, 15); + + private String title; + private String content; + private UITextArea titleTextArea; + private UITextArea contentTextArea; + private UIButton closeButton; + + public BubbleWithClose(String title, String content) { + this(title, content, TailDirection.LEFT, 0.5f); + } + + public BubbleWithClose(String title, String content, TailDirection tailDirection, float tailStart) { + super(tailDirection, tailStart); + this.title = title; + this.content = content; + this.initComponent(); + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + protected void initComponent() { + this.setLayout(getLayoutManager()); + createCloseButton(); + createContentPane(); + this.addComponentListener(new ComponentAdapter() { + @Override + public void componentShown(ComponentEvent e) { + super.componentShown(e); + } + + @Override + public void componentResized(ComponentEvent e) { + Rectangle rectBounds = getRectBounds(); + Dimension buttonSize = closeButton.getPreferredSize(); + + closeButton.setBounds(getWidth() - 10 - buttonSize.width, rectBounds.y + (HEADER_HEIGHT - buttonSize.height) / 2, buttonSize.width, buttonSize.height); + + Dimension titleSize = new Dimension(0,0); + Dimension contentSize = new Dimension(0, 0); + if (titleTextArea != null) { + titleSize = calTextSize(titleTextArea, getTextAreaMaxWidth()); + int x = rectBounds.x + (rectBounds.width - getHorizontalInsets() - titleSize.width) / 2 + DEFAULT_INSET.left; + int y = rectBounds.y + HEADER_HEIGHT + DEFAULT_INSET.top; + titleTextArea.setBounds(x, y, titleSize.width, titleSize.height); + } + + if (contentTextArea != null) { + contentSize = calTextSize(contentTextArea, getTextAreaMaxWidth()); + int x = rectBounds.x + (rectBounds.width - getHorizontalInsets() - contentSize.width) / 2 + DEFAULT_INSET.left; + int y = rectBounds.y = HEADER_HEIGHT + DEFAULT_INSET.top + (titleSize.height == 0 ? 0 : titleSize.height + 10); + contentTextArea.setBounds(x,y, contentSize.width, contentSize.height); + } + setSize(getPreferredSize().width, getPreferredSize().height); + repaint(); + } + }); + } + + private void createCloseButton() { + closeButton = new UIButton(); + closeButton.setIcon(IOUtils.readIcon("/com/fr/design/mainframe/guide/close.png")); + closeButton.set4ToolbarButton(); + closeButton.setPreferredSize(new Dimension(16, 16)); + closeButton.setRolloverEnabled(false); + closeButton.setPressedPainted(false); + this.add(closeButton); + } + + public void addCloseActionListener(ActionListener actionListener) { + closeButton.addActionListener(actionListener); + + } + + public void removeCloseActionListener(ActionListener actionListener){ + closeButton.removeActionListener(actionListener); + } + + private void createContentPane() { + createTitleTextArea(); + createContentTextArea(); + } + + private void createTitleTextArea() { + if (StringUtils.isNotEmpty(title)) { + titleTextArea = createTextArea(title, FONT, TITLE_COLOR); + this.add(titleTextArea); + } + + } + + private void createContentTextArea() { + if (StringUtils.isNotEmpty(content)) { + contentTextArea = createTextArea(content, FONT, CONTENT_COLOR); + this.add(contentTextArea); + } + } + + private UITextArea createTextArea(String str, Font font, Color foreground) { + UITextArea textArea= new UITextArea(str){ + @Override + public Insets getInsets() { + return new Insets(0, 0, 0, 0); + } + }; + textArea.setEnabled(true); + textArea.setBorder(null); + textArea.setFont(font); + textArea.setOpaque(false); + textArea.setForeground(foreground); + return textArea; + } + + protected int getDefaultHeight() { + Dimension titleSize = calTextSize(titleTextArea, getDefaultTextAreaMaxWidth()); + Dimension contentSize = calTextSize(contentTextArea, getDefaultTextAreaMaxWidth()); + return (isTailVertical() ? TAIL_HEIGHT : 0) + HEADER_HEIGHT + titleSize.height + (titleSize.height == 0 ? 0 : 5) + contentSize.height + getVerticalInsets(); + } + + private LayoutManager getLayoutManager() { + return new LayoutManager() { + @Override + public void addLayoutComponent(String name, Component comp) { + } + + @Override + public void removeLayoutComponent(Component comp) { + + } + + @Override + public Dimension preferredLayoutSize(Container parent) { + return parent.getPreferredSize(); + } + + @Override + public Dimension minimumLayoutSize(Container parent) { + return null; + } + + @Override + public void layoutContainer(Container parent) { + + } + }; + } + + @Override + protected void paintBubbleBg(Graphics2D g2) { + super.paintBubbleBg(g2); + paintHeaderBg(g2); + } + + private void paintHeaderBg(Graphics2D g2) { + g2.setColor(HEADER_COLOR); + Rectangle bounds = getRectBounds(); + g2.fillRoundRect(bounds.x, bounds.y, bounds.width, HEADER_HEIGHT, 10, 10); + } + + private int countLines(JTextArea textArea, int max_width) { + AttributedString text = new AttributedString(textArea.getText()); + text.addAttribute(TextAttribute.FONT, textArea.getFont()); + FontRenderContext frc = textArea.getFontMetrics(textArea.getFont()) + .getFontRenderContext(); + AttributedCharacterIterator charIt = text.getIterator(); + LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(charIt, frc); + lineMeasurer.setPosition(charIt.getBeginIndex()); + int lines = 0; + while (lineMeasurer.getPosition() < charIt.getEndIndex()) { + lineMeasurer.nextLayout(max_width); + lines++; + } + return lines; + } + + private Dimension calTextSize(JTextArea textArea, int maxWidth) { + if (textArea == null) { + return new Dimension(0, 0); + } + FontMetrics fontMetrics = GraphHelper.getFontMetrics(textArea.getFont()); + int line = countLines(textArea, maxWidth); + int width = maxWidth; + if (line == 1) { + width = fontMetrics.stringWidth(textArea.getText()); + } + int height = fontMetrics.getHeight() * line; + return new Dimension(width, height); + } + + private int getTextAreaMaxWidth() { + return getWidth() - (isTailHorizontal() ? TAIL_HEIGHT : 0) -getHorizontalInsets(); + } + + private int getDefaultTextAreaMaxWidth() { + return BUBBLE_WIDTH - getHorizontalInsets(); + } + + private int getHorizontalInsets() { + return DEFAULT_INSET.left + DEFAULT_INSET.right; + } + + private int getVerticalInsets() { + return DEFAULT_INSET.top + DEFAULT_INSET.bottom; + } + +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/guide/utils/ScreenImage.java b/designer-base/src/main/java/com/fr/design/mainframe/guide/utils/ScreenImage.java new file mode 100644 index 0000000000..7c722d7de5 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/guide/utils/ScreenImage.java @@ -0,0 +1,98 @@ +package com.fr.design.mainframe.guide.utils; + +import javax.swing.JComponent; +import javax.swing.SwingUtilities; +import java.awt.AWTException; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Component; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Robot; +import java.awt.image.BufferedImage; + +public class ScreenImage { + + public static BufferedImage createImage(JComponent component) { + return ScreenImage.createImage(component, getRegion(component)); + } + + public static BufferedImage createImage(JComponent component, Rectangle region) { + if (! component.isDisplayable()) { + Dimension d = component.getSize(); + + if (d.width == 0 || d.height == 0) { + d = component.getPreferredSize(); + component.setSize( d ); + } + + layoutComponent( component ); + } + + BufferedImage image = new BufferedImage(region.width, region.height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + + if (! component.isOpaque()) { + g2d.setColor( component.getBackground() ); + g2d.fillRect(region.x, region.y, region.width, region.height); + } + + g2d.translate(-region.x, -region.y); + component.print( g2d ); + g2d.dispose(); + return image; + } + + public static BufferedImage createImage(Component component) + throws AWTException { + Point p = new Point(0, 0); + SwingUtilities.convertPointToScreen(p, component); + Rectangle region = component.getBounds(); + region.x = p.x; + region.y = p.y; + return ScreenImage.createImage(region); + } + + public static BufferedImage createImageWithModal(JComponent component) { + Rectangle region = getRegion(component); + BufferedImage image = new BufferedImage(region.width, region.height, BufferedImage.TYPE_INT_RGB); + Graphics2D g2d = image.createGraphics(); + g2d.drawImage(createImage(component), 0, 0, null); + g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f)); + g2d.setColor(Color.BLACK); + g2d.fillRect(0, 0, region.width, region.height); + g2d.dispose(); + return image; + } + + public static BufferedImage createImage(Rectangle region) + throws AWTException { + BufferedImage image = new Robot().createScreenCapture( region ); + return image; + } + + private static Rectangle getRegion(Component component) { + Dimension d = component.getSize(); + if (d.width == 0 || d.height == 0) { + d = component.getPreferredSize(); + component.setSize( d ); + } + return new Rectangle(0, 0, d.width, d.height); + } + + static void layoutComponent(Component component) { + synchronized (component.getTreeLock()) { + component.doLayout(); + + if (component instanceof Container) { + for (Component child : ((Container)component).getComponents()) { + layoutComponent(child); + } + } + } + } + +} \ No newline at end of file diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_down.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..cf65b3bc4f920ad8e0e3a0ece3bed5692bdc2f82 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`nVv3=Ar}70DGLM)f>z8=w2-`T z-(It&u`$tfVxmw~#Ek81OT<)pe*IRzZfx);|3E%RvU6f$p0W?Go5CY=mQdq1?u2DY z<}AU+e#{O+E7}sS_grUaQRLw{QRl`#|KXGg9X<{cr!@GO8P4k}3a0T^=K(Ec@O1Ta JS?83{1OTdcG0Xq} literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_right.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/arrow_right.png new file mode 100644 index 0000000000000000000000000000000000000000..a1ac42cfea737bcdea8089102f4c1157e8b2b5c6 GIT binary patch literal 153 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`37#&FAr}70DGLM)f>z9DT+R6R z?_?(~?$6f*k}dR@{}~$?n2CrzGZQjm>1R|}+rq1uz>w(NVZg9o(M77^wm_9+gSA0t z0pq_J3sOyl{FomsI6Cc;hTo}{N&1^?j_wp>IHF}G82u_W5@;8Lr>mdKI;Vst0CD0k AO#lD@ literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/close.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/close.png new file mode 100644 index 0000000000000000000000000000000000000000..22e3caa538957be586f638a6ede20a45f4bc1e17 GIT binary patch literal 375 zcmV--0f_#IP)6EdM zj9n{Et)!^U^PML*MDry+2={UCIp==l-Hw!!q9}4~+ukNgvNvuxp63lM%bFPx$pGibPR5(wqlfP>eaS+AdoA2J{A|%&{q!fh|R$>|4aEVI5!mHH3AcbYH^AE5S z3k#9!v@&2}6BG#^1OyvNl|s-)3l*EoiFe8F&Xc`8!?7kO{+RB|@cqoZH#0;f7Vh^> z9E<8_9GTIfA}s?Ak$=wyJFjlcd>Q}3gU7l;vpYyY)+r@`T?Tl%%f0@@s0cR|cnJ1aTZg>en?PcIu&&kM*9&}{dcWH<$(J#2Vl&_S$9 z`TNf5bccoJxK=wy=i?(QSAoK=OxO3*^bIez(`JtB5@5G}Q+a#evsP^3HGnz5ZwEv# zxZLh93w8&<*$IHRds&>sQF@icJxGQ0ZNhKz!h`;jTX+g+W@OYOkXHaTO5gM$@~QKR z^29xV2w>z9d**rT7l4^9z!5q048S*-FH)G7>Zyh2*Ujw+9Dx3`R&u*M0 zv;kki8h0IAM%k05O1S<>5jdjPair>A$SPx$OG!jQR5(wqlQBp_Q51&%|AsFxF7jRY>4p8MbPoqO&%hv-|03x$KWGbIDkh1zm= zZwIZH(mLzCF8nsKGO0-?3Nb{Zw17o`(;Z#U1n%TA52{gkwW`>s+g3V&tjv7Gj$)Nm21XmI4kKu;!-hVmHSg`+sT|0 zCv{75Pp+N{nb8WpoIhGo7~~}^{N~&Rb|AQii?lr!sM3p0tO^VlHm#kXBKM!APMsq{ zbqQBYo;7DEoWtdIVaqBF1UPXLFgg~o;k^Llqs9Y~;xZe90FpNf@x4`pH0J~0)F8|a vCk0=h=(~)*8)+!5+=cvqn0ku;*Kz&=!aDZfq)LR^00000NkvXXu0mjfk3g!- literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/complete_some.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/complete_some.png new file mode 100644 index 0000000000000000000000000000000000000000..b3443318bde309459c48d35b1f453420742b2647 GIT binary patch literal 567 zcmV-70?7S|P)Px$@kvBMR5(wSlh11tQ5?i)-lk~{QoJdq`2$47f}o%xja8^<55<58UPLPt@uDZa z6fYhG1<6U<;zjUaC7?Yh{zwolco2%By$WsgCL$8-X7@X?yG=~fR^mJD+u8SdGwghc zwzc=ZdNVaYCkN7?NNzmWGU2%DR6xqQ~&2In?W8WB_gm`!w52~LYF!R$x5 znx4lsTBIa^ak)cVYyC)d?Utb^qC~oBXWpAgHWwa{tUzIe4i^#B-@p2rnTsGXAb)ph zJ3a1`>6QmK{9?4L(jG0^AD*ZWgs>-cyrfXG<@4R27-#++?1c2v1Bcc-+yEvJJq0;f z>q&iNd^>r@h-~Ft`qWxCx4#>h^dC5(D(M~vCYN?ln1S;ym(Qjibppsw1t(zPO%caq z2O#I1<3UL$B@Kh@1N4nz$%629Wo*NjR`B42HXG`TkouP+-~)g_Kzfw}(g!dVO2yZs z-Sd&20Q@S1T}cGsEri~G+9w6!GoVF~IY4~^_Afio1%YFo-T_pMf8)klu*axp zacMhRG)Er$)svcS7Rhs|qanf@kp9+tEf^_+S7bI57imS6z4Q@D*VlJKBY72}|9nN2 z8<1$2oO3xrLoyZMxFvDK?}R-hy~k#@uzHwUtp4vf{{dxj0PeJ?a?bz&002ovPDHLk FV1jwI{5Svr literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/guide.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/guide.png new file mode 100644 index 0000000000000000000000000000000000000000..ef5a1978346bb58f15bcd8965fe4ee072eeb6dac GIT binary patch literal 1470 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`Ea{HEjtmSN`?>!lvI6-E$sR$z z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBDAAG{;hE;^%b*2hb1<+n z3NbJPS&Tr)z$nE4G7ZRL@M4sPvx68lplX;H7}_%#SfFa6fHVkr05M1pgl1mAh%j*h z6I`{x0%imor0saV%ts)_S>O>_%)r1c48n{Iv*t)JFfbWphD4M^`1)8S=jZArrsOB3 z>Q&?xfOIj~R9FF-xv3?I3Kh9IdBs*0wn|_XRzNmLSYJs2tfVB{Rw=?aK*2e`C{@8! z&qU8a$&O1wLBXadCCw_x#SN+*$g@?-C@Cqh($_C9FV`zK*2^zS*Eh7ZwA42+(l;{F z1**_3uFNY*tkBIXR)!b?Gsh*hIJqdZpd>RtPXT0ZVp4u-iLH_n$Rap^xU(cP4PjGW zG1OZ?59)(t^bPe4^s#A6t;oco4I~562KE=kIvbE-R{lkqsXzyVoMmTd1GWG~4BJHW(X0^`J{&)fkCOBa#GEqm4c& zOp(GE5`kb*U<}%E+33SlgB{nGlzaZbw3FfK;uvBf*cyC3Ti8)#-psi=ZYX znUf~neA9D3+h`%{QTD^tzw7qD-}^Mj$5G(Su63_(yx(8AG(@KD;k`iV<^2i~6$fVQ zWDR`Cc}aF*hE8@tzu`WyHu*4x;583JB#XL>%hraizIyze@U)XD&t#5I3>1;|(9|!` zi&K90W{0reNr~fy;T+F*zfYIvI`~XLHuJ2?^F7nORHtiCJvG5-(G7Qz*SqqPj8-`? zF)x+iVGEwT>A#o()9kljcWluM6Np;=>-KHw)7KIzt>)Uz^ovikwwB(%@O5!so|)A9 zx=8EuPm4Ymu5n)bc(uf1U7qD1IV_ku8y0_S+;j5s9py7ec-OX{fBm&;wt<1i1D^Ji ze5>X(e>T(-;aYArw=c_V_T3z_-6=fLM|YjqI2vPNw@o=iv-)zxbJ=8`MvEh1G8Xb7 ztFKo7tog^1bDrUql2xJ??}dex{!??9CUpvZ*}!Rci>dpa zvgqwmYZ(LfpWXR6BRl)E@w*gp^I-Rd;ZqjG=p8R{X(?NDG2_P78<$h}&EMS=nI7gTe~DWM4fZIt{X literal 0 HcmV?d00001 diff --git a/designer-base/src/main/resources/com/fr/design/mainframe/guide/success.png b/designer-base/src/main/resources/com/fr/design/mainframe/guide/success.png new file mode 100644 index 0000000000000000000000000000000000000000..efd7a04f4b1a52f2aefb1d8b0eec90d75098ac65 GIT binary patch literal 31304 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 F{y)$y7#9El literal 0 HcmV?d00001 diff --git a/designer-realize/src/main/java/com/fr/design/mainframe/guide/entry/GuideEntryPane.java b/designer-realize/src/main/java/com/fr/design/mainframe/guide/entry/GuideEntryPane.java new file mode 100644 index 0000000000..2058dc6012 --- /dev/null +++ b/designer-realize/src/main/java/com/fr/design/mainframe/guide/entry/GuideEntryPane.java @@ -0,0 +1,79 @@ +package com.fr.design.mainframe.guide.entry; + +import com.fr.design.constants.UIConstants; +import com.fr.design.dialog.BasicPane; +import com.fr.design.gui.ibutton.UIButton; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.guide.collect.GuideCollector; +import com.fr.design.mainframe.guide.ui.BubbleHintDialog; +import com.fr.design.mainframe.guide.ui.GuideManageDialog; +import com.fr.general.IOUtils; + +import javax.swing.SwingUtilities; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.Point; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class GuideEntryPane extends BasicPane { + private static GuideEntryPane guideEntryPane; + + private UIButton button; + + public static GuideEntryPane getGuideEntryPane() { + if (guideEntryPane == null) { + guideEntryPane = new GuideEntryPane(); + } + return guideEntryPane; + } + @Override + protected String title4PopupWindow() { + return null; + } + + private GuideEntryPane() { + setPreferredSize(new Dimension(24, 24)); + setLayout(new BorderLayout()); + button = new UIButton(); + button.setIcon(IOUtils.readIcon("/com/fr/design/mainframe/guide/guide.png")); + button.setToolTipText(Toolkit.i18nText(Toolkit.i18nText("Fine-Design_Guide_Entry_Name"))); + button.set4ToolbarButton(); + button.setRolloverEnabled(false); + this.add(button); + button.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + GuideManageDialog.getInstance().showDialog(); + } + }); + this.setBackground(UIConstants.TEMPLATE_TAB_PANE_BACKGROUND); + this.addAncestorListener(new AncestorListener() { + @Override + public void ancestorAdded(AncestorEvent event) { + if (!GuideCollector.getInstance().isShowHint()) { + BubbleHintDialog dialog = new BubbleHintDialog(DesignerContext.getDesignerFrame()); + Point point = new Point(0,0); + SwingUtilities.convertPointToScreen(point, GuideEntryPane.this); + Dimension size = GuideEntryPane.this.getSize(); + dialog.setLocationRelativeTo(GuideEntryPane.this); + dialog.setLocation(point.x - 187,point.y + size.height); + dialog.setVisible(true); + } + } + + @Override + public void ancestorRemoved(AncestorEvent event) { + } + + @Override + public void ancestorMoved(AncestorEvent event) { + } + }); + + } + +} diff --git a/designer-realize/src/main/java/com/fr/start/MainDesigner.java b/designer-realize/src/main/java/com/fr/start/MainDesigner.java index e52ae1530d..a2147e8810 100644 --- a/designer-realize/src/main/java/com/fr/start/MainDesigner.java +++ b/designer-realize/src/main/java/com/fr/start/MainDesigner.java @@ -32,6 +32,7 @@ import com.fr.design.mainframe.JWorkBook; import com.fr.design.mainframe.alphafine.component.AlphaFinePane; import com.fr.design.mainframe.bbs.UserInfoLabel; import com.fr.design.mainframe.bbs.UserInfoPane; +import com.fr.design.mainframe.guide.entry.GuideEntryPane; import com.fr.design.module.ChartPreStyleAction; import com.fr.design.notification.SnapChatAllTypes; import com.fr.design.notification.ui.NotificationCenterPane; @@ -424,6 +425,10 @@ public class MainDesigner extends BaseDesigner { return AlphaFinePane.getAlphaFinePane(); } + public Component createGuideEntryPane() { + return GuideEntryPane.getGuideEntryPane(); + } + public Component createNotificationCenterPane(){ return NotificationCenterPane.getNotificationCenterPane(); }