绘图板程序设计及其具体实现 第七篇

主程序

以上所有的铺垫工作都已经完毕,接下来主程序的绘制异常简单,如果有不明白的地方可以在返回之前的示例中查看。实际上,最后的测试阶段会发现Bug大都出现在前面的铺垫中,主程序只是将它们做一个有机的相加的过程。下面我将对主程序的每一个部分做详细解释。

字段声明

  • 主程序中的字段一览表
修饰符 类型 名称 说明
protected ArrayList< ImageElement> elements 存储图形元素的列表
protected HashMap< String, Strategy> strategy 存储所有策略的字典
protected Strategy currentStrategy 当前所采用的的绘图策略
protected Color currentColor 当前的绘图颜色
protected boolean similar 是否开启快捷选择操作
protected JButton backgroundButton 修改画板背景色的按钮
protected JButton colorButton 修改当前绘图颜色的按钮
protected JButton similarButton 开启/关闭快捷选择操作的按钮
protected BufferedImage lastImage 打开的以保存图像

构造函数

由于主程序继承自SwingFramework类,所以构造函数只需要对一些继承自父类的元素稍作修改即可。

  • 主程序构造函数源代码
public Editor() {
        appBorder = new Color(0xFFEBCD);
        appBackground = Color.WHITE;
        appFont = new Font("Courier New", Font.PLAIN, 20);
        appWidth = 1080;
        appHeight = 720;
        appWorldWidth = 16.0f;
        appWorldHeight = 9.0f;
        appSleep = 10L;
        appMaintainRatio = true;
        appBorderScale = 0.95f;
        appTitle = "Editor";
        currentColor = Color.BLACK;
        similar = false;
    }

GUI设计

在父类中提供了onCreateAndShowGUI方法供子类继承来设计程序界面,这是一个很长的方法,但是很好理解。它在主面板里增加了菜单栏和工具栏,在菜单栏中增加新建,保存,打开,关闭,说明,关于等功能,在工具栏中为每一个策略和一些其他小功能增添了按钮,在改变策略时改变currentStrategy字段即可。

  • onCreateAndShowGUI方法源代码
@Override
    protected void onCreateAndShowGUI() {
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("File");
        JMenuItem item = new JMenuItem(new AbstractAction("New") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()||empty()) {
                    onNew();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onNew();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Open") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()||empty()) {
                    onOpen();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onOpen();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Save") {
            @Override
            public void actionPerformed(ActionEvent e) {
                while (!saved()) {
                    save();
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Exit") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor.this.dispatchEvent(new WindowEvent(
                        Editor.this, WindowEvent.WINDOW_CLOSING
                ));
            }
        });
        menu.add(item);
        menuBar.add(menu);
        menu = new JMenu("Help");
        item = new JMenuItem(new AbstractAction("Instruction") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "Instruction of this app!!!",
                        "Instruction", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("About") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "About this app!!!",
                        "About", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        menuBar.add(menu);
        setJMenuBar(menuBar);

        JToolBar bar = new JToolBar();
        bar.setFloatable(false);
        backgroundButton = new JButton("■");
        backgroundButton.setForeground(appBackground);
        backgroundButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    appBackground = color;
                }
                backgroundButton.setForeground(appBackground);
                canvas.setBackground(appBackground);
            }
        });
        bar.add(backgroundButton);
        colorButton = new JButton("■");
        colorButton.setForeground(currentColor);
        colorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    currentColor = color;
                }
                colorButton.setForeground(currentColor);
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(colorButton);
        JButton b = new JButton("•");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("points strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("-");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("line strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("△");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("□");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("○");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("O");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("◆");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill polygon strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("▲");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("■");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("●");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("Θ");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("abc");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("string strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        similarButton = new JButton("×");
        similarButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                similar = !similar;
                if (similar) {
                    similarButton.setForeground(Color.RED);
                } else {
                    similarButton.setForeground(Color.BLACK);
                }
            }
        });
        bar.add(similarButton);
        b = new JButton("⊙");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("null strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        getMainPanel().add(bar, BorderLayout.NORTH);
    }

菜单操作方法

在onCreateAndShowGUI方法的菜单操作中只是调用了执行操作的方法,而没有对具体的文件操作做具体实现,下面是对这些方法的详解。

  • 菜单操作方法一览表
修饰符 返回值 函数名 参数 说明
protected boolean empty () 判断当前画板是否为空
protected boolean saved () 判断当前图像是否保存
protected void save () 保存当前绘制图像
protected void onNew () 新建画板并还原默认设置
protected void onOpen () 新建画板并打开指定图像
  • 菜单操作方法源代码
protected boolean empty() {
        if (elements.size() == 0) {
            return true;
        } else {
            for (ImageElement element : elements) {
                if (!element.empty()) {
                    return false;
                }
            }
            return true;
        }
    }

    protected boolean saved() {
        if (empty()) {
            return true;
        }else {
            return elements.get(elements.size() - 1) instanceof SaveImageElement;
        }
    }

    protected void save() {
        BufferedImage image = new BufferedImage(getScreenWidth(), getScreenHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        Matrix3x3f view = getViewportTransform();
        g2d.setColor(appBackground);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        if (lastImage != null) {
            g2d.drawImage(lastImage, 0, 0, image.getWidth(), image.getHeight(), null);
        }
        for (ImageElement element : elements) {
            element.draw(g2d, view);
        }
        g2d.dispose();
        String fileName = (String) JOptionPane.showInputDialog(Editor.this, "文件名:",
                "保存", JOptionPane.PLAIN_MESSAGE, null,null,"new");
        if (fileName == null) {
            return;
        }
        fileName = fileName + ".jpg";
        File file = new File(fileName);
        try {
            if (!ImageIO.write(image, "jpg", file)) {
                throw new IOException("No 'jpg' image writer found");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            elements.add(new SaveImageElement());
        }
    }

    protected void onNew() {
        elements.clear();
        currentColor = Color.BLACK;
        colorButton.setForeground(currentColor);
        currentStrategy.finishDrawing();
        currentStrategy = strategy.get("null strategy");
        currentStrategy.setColor(currentColor);
    }

    protected void onOpen() {
        onNew();
        String fileName = JOptionPane.showInputDialog(Editor.this, "文件名:",
                "打开", JOptionPane.PLAIN_MESSAGE);
        if (fileName == null) {
            return;
        }
        if (fileName.endsWith(".jpg") || fileName.endsWith(".png")) {
            try {
                lastImage = ImageIO.read(new File(fileName));
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(
                        Editor.this, "No such image exists!!!",
                        "Warning", JOptionPane.INFORMATION_MESSAGE);
                e1.printStackTrace();
            }
        } else {
            JOptionPane.showMessageDialog(
                    Editor.this, "Can't solve image type!!!",
                    "Warning", JOptionPane.INFORMATION_MESSAGE);
        }
    }

初始化操作

初始化操作调用继承自Framework类的initialize方法,其主要作用为向策略字典中添加所有的绘制策略,是本程序中最难理解的一个函数。

每个BeginEnd策略的初始化框架如下:

strategy.put("strategy name",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        //some thing to draw
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                       //some thing for similar
                    }
                });
            }
        });

在字典中加入匿名的策略,需要重写抽象策略的addElement方法,而addElement方法又需要一个匿名的图形元素,其中重写了draw方法和similar方法。实际上就是一种不同的draw方法和similar方法,决定了一个图形元素,同时也决定了一种绘图策略。

  • initialize方法源代码
@Override
    protected void initialize() {
        super.initialize();
        elements = new ArrayList<ImageElement>();
        strategy = new HashMap<>();
        strategy.put("null strategy",
                new NullStrategy(this, keyboard, mouse, elements));
        strategy.put("points strategy",
                new PointsStrategy(this, keyboard, mouse, elements));
        strategy.put("line strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawPolygon(g2d, new Vector2f[]{begin, end}, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        }else if(center.similar(mousePos,EPSILON)){
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill polygon strategy",
                new FillPolygonStrategy(this, keyboard, mouse, elements));
        strategy.put("string strategy",
                new StringStrategy(this, keyboard, mouse, elements));
        currentStrategy = strategy.get("null strategy");
    }

主程序输入处理

由于主程序继承自SwingFramework类,那么就需要实现Framework类里的processInput方法来处理输入。因为之前已经把所有的输入处理都交给了不同的策略,所以这一步非常的简单,整个方法只有以下两行。

@Override
    protected void processInput(float delta) {
        super.processInput(delta);
        currentStrategy.processInput();
    }

主程序渲染处理

由于主程序继承自SwingFramework类,那么就需要实现Framework类里的render方法来处理渲染。这里将渲染处理分为3部分,打开的图像文件的渲染,图形元素列表的渲染,和快捷选择提示圆的渲染。这一步也非常简单,因为具体的实现方法之前已经定义好了,所以这里只需要调用每一个图形元素的draw方法即可。具体代码如下。

protected void renderLastImage(Graphics g) {
        if (lastImage != null) {
            g.drawImage(lastImage, 0, 0, getScreenWidth(), getScreenHeight(),null);
        }
    }

    @Override
    protected void render(Graphics g) {
        super.render(g);
        renderLastImage(g);
        Matrix3x3f view = getViewportTransform();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            element.draw((Graphics2D)g, view);
        }
        if (similar) {
            renderSimilar(g);
        }
    }

    protected void renderSimilar(Graphics g) {
        Matrix3x3f view = getViewportTransform();
        Vector2f mousePos = getWorldMousePosition();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            if (element instanceof Similarable) {
                Vector2f tmpPos = ((Similarable) element).similar(mousePos);
                if (tmpPos != null && tmpPos.sub(mousePos).len() > Vector2f.EPSILON) {
                    Utility.fillSimilarCircle((Graphics2D) g, tmpPos, view);
                    if (mouse.buttonDownOnce(MouseEvent.BUTTON2)) {
                        currentStrategy.similar(tmpPos, getViewportTransform());
                    }
                }
            }
        }
    }

主程序主函数

其实到这里,整个程序已经结束了,但是要有主函数,程序才能运行。其实应该可以想到,非常简单,主函数只有一行。

public static void main(String[] args) {
        launchApp(new Editor());
    }

launchApp即可,Now,You can enjoy it!


附录:主程序源代码

import Rendering.element.BeginEndImageElement;
import Rendering.element.ImageElement;
import Rendering.element.SaveImageElement;
import Rendering.element.Similarable;
import Rendering.strategy.*;
import Rendering.utils.Matrix3x3f;
import Rendering.utils.SwingFramework;
import Rendering.utils.Utility;
import Rendering.utils.Vector2f;
import Rendering.strategy.FillPolygonStrategy;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;

public class Editor extends SwingFramework {
    protected ArrayList<ImageElement> elements;
    protected HashMap<String, Strategy> strategy;
    protected Strategy currentStrategy;
    protected Color currentColor;
    protected boolean similar;
    protected JButton backgroundButton;
    protected JButton colorButton;
    protected JButton similarButton;
    protected BufferedImage lastImage;


    public Editor() {
        appBorder = new Color(0xFFEBCD);
        appBackground = Color.WHITE;
        appFont = new Font("Courier New", Font.PLAIN, 20);
        appWidth = 1080;
        appHeight = 720;
        appWorldWidth = 16.0f;
        appWorldHeight = 9.0f;
        appSleep = 10L;
        appMaintainRatio = true;
        appBorderScale = 0.95f;
        appTitle = "Editor";
        currentColor = Color.BLACK;
        similar = false;
    }

    @Override
    protected void onCreateAndShowGUI() {
        JMenuBar menuBar = new JMenuBar();
        JMenu menu = new JMenu("File");
        JMenuItem item = new JMenuItem(new AbstractAction("New") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()) {
                    onNew();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onNew();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Open") {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (saved()) {
                    onOpen();
                } else {
                    int isSave = JOptionPane.showConfirmDialog(Editor.this, "是否保存文件?",
                            "保存", JOptionPane.YES_NO_OPTION);
                    if (isSave == JOptionPane.NO_OPTION) {
                        onOpen();
                    } else {
                        save();
                    }
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Save") {
            @Override
            public void actionPerformed(ActionEvent e) {
                while (!saved()) {
                    save();
                }
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("Exit") {
            @Override
            public void actionPerformed(ActionEvent e) {
                Editor.this.dispatchEvent(new WindowEvent(
                        Editor.this, WindowEvent.WINDOW_CLOSING
                ));
            }
        });
        menu.add(item);
        menuBar.add(menu);
        menu = new JMenu("Help");
        item = new JMenuItem(new AbstractAction("Instruction") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "Instruction of this app!!!",
                        "Instruction", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        item = new JMenuItem(new AbstractAction("About") {
            @Override
            public void actionPerformed(ActionEvent e) {
                JOptionPane.showMessageDialog(
                        Editor.this, "About this app!!!",
                        "About", JOptionPane.INFORMATION_MESSAGE
                );
            }
        });
        menu.add(item);
        menuBar.add(menu);
        setJMenuBar(menuBar);

        JToolBar bar = new JToolBar();
        bar.setFloatable(false);
        backgroundButton = new JButton("■");
        backgroundButton.setForeground(appBackground);
        backgroundButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    appBackground = color;
                }
                backgroundButton.setForeground(appBackground);
                canvas.setBackground(appBackground);
            }
        });
        bar.add(backgroundButton);
        colorButton = new JButton("■");
        colorButton.setForeground(currentColor);
        colorButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                JColorChooser chooser=new JColorChooser();
                Color color = chooser.showDialog(
                        Editor.this, "选取颜色", Color.BLACK);
                if (color != null) {
                    currentColor = color;
                }
                colorButton.setForeground(currentColor);
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(colorButton);
        JButton b = new JButton("•");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("points strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("-");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("line strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("△");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("□");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("○");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("O");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("◆");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill polygon strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("▲");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill triangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("■");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill rectangle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("●");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill circle strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("Θ");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("fill oval strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        b = new JButton("abc");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("string strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        similarButton = new JButton("×");
        similarButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                similar = !similar;
                if (similar) {
                    similarButton.setForeground(Color.RED);
                } else {
                    similarButton.setForeground(Color.BLACK);
                }
            }
        });
        bar.add(similarButton);
        b = new JButton("⊙");
        b.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                currentStrategy.finishDrawing();
                currentStrategy = strategy.get("null strategy");
                currentStrategy.setColor(currentColor);
            }
        });
        bar.add(b);
        getMainPanel().add(bar, BorderLayout.NORTH);
    }

    protected boolean empty() {
        if (elements.size() == 0) {
            return true;
        } else {
            for (ImageElement element : elements) {
                if (!element.empty()) {
                    return false;
                }
            }
            return true;
        }
    }

    protected boolean saved() {
        if (empty()) {
            return true;
        }else {
            return elements.get(elements.size() - 1) instanceof SaveImageElement;
        }
    }

    protected void save() {
        BufferedImage image = new BufferedImage(getScreenWidth(), getScreenHeight(), BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        Matrix3x3f view = getViewportTransform();
        g2d.setColor(appBackground);
        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
        if (lastImage != null) {
            g2d.drawImage(lastImage, 0, 0, image.getWidth(), image.getHeight(), null);
        }
        for (ImageElement element : elements) {
            element.draw(g2d, view);
        }
        g2d.dispose();
        String fileName = (String) JOptionPane.showInputDialog(Editor.this, "文件名:",
                "保存", JOptionPane.PLAIN_MESSAGE, null,null,"new");
        if (fileName == null) {
            return;
        }
        fileName = fileName + ".jpg";
        File file = new File(fileName);
        try {
            if (!ImageIO.write(image, "jpg", file)) {
                throw new IOException("No 'jpg' image writer found");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            elements.add(new SaveImageElement());
        }
    }

    protected void onNew() {
        elements.clear();
        currentColor = Color.BLACK;
        colorButton.setForeground(currentColor);
        currentStrategy.finishDrawing();
        currentStrategy = strategy.get("null strategy");
        currentStrategy.setColor(currentColor);
    }

    protected void onOpen() {
        onNew();
        String fileName = JOptionPane.showInputDialog(Editor.this, "文件名:",
                "打开", JOptionPane.PLAIN_MESSAGE);
        if (fileName == null) {
            return;
        }
        if (fileName.endsWith(".jpg") || fileName.endsWith(".png")) {
            try {
                lastImage = ImageIO.read(new File(fileName));
            } catch (IOException e1) {
                JOptionPane.showMessageDialog(
                        Editor.this, "No such image exists!!!",
                        "Warning", JOptionPane.INFORMATION_MESSAGE);
                e1.printStackTrace();
            }
        } else {
            JOptionPane.showMessageDialog(
                    Editor.this, "Can't solve image type!!!",
                    "Warning", JOptionPane.INFORMATION_MESSAGE);
        }
    }

    @Override
    protected void initialize() {
        super.initialize();
        elements = new ArrayList<ImageElement>();
        strategy = new HashMap<>();
        strategy.put("null strategy",
                new NullStrategy(this, keyboard, mouse, elements));
        strategy.put("points strategy",
                new PointsStrategy(this, keyboard, mouse, elements));
        strategy.put("line strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawPolygon(g2d, new Vector2f[]{begin, end}, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        }else if(center.similar(mousePos,EPSILON)){
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.drawOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill triangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillTriangle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f tmp = end.sub(begin);
                        Vector2f topTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() + Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        Vector2f bottomTmp = begin.add(
                                Vector2f.polar(
                                        new Double(tmp.angle() - Math.PI / 6.0).floatValue(),
                                        new Double(tmp.len() * Math.sqrt(3.0)).floatValue()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if (topTmp.similar(mousePos,EPSILON)) {
                            return topTmp;
                        } else if (bottomTmp.similar(mousePos,EPSILON)) {
                            return bottomTmp;
                        }else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill rectangle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillRect(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f other = begin.add(new Vector2f(end.sub(begin).x, 0));
                        Vector2f another = end.sub(new Vector2f(end.sub(begin).x, 0));
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (end.similar(mousePos,EPSILON)) {
                            return end;
                        } else if(other.similar(mousePos,EPSILON)){
                            return other;
                        } else if (another.similar(mousePos, EPSILON)) {
                            return another;
                        } else if (center.similar(mousePos, EPSILON)) {
                            return center;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill circle strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillCircle(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f circle = mousePos.sub(begin).norm();
                        circle = begin.add(circle.mul(end.sub(begin).len()));
                        if (begin.similar(mousePos,EPSILON)) {
                            return begin;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill oval strategy",
                new BeginEndStrategy(this, keyboard, mouse, elements) {
            @Override
            protected void addElement() {
                elements.add(new BeginEndImageElement(begin, end, color) {
                    @Override
                    public void draw(Graphics2D g2d, Matrix3x3f view) {
                        Utility.fillOval(g2d, begin, end, view, color);
                    }

                    @Override
                    public Vector2f similar(Vector2f mousePos) {
                        Vector2f center = begin.add(end.sub(begin).div(2));
                        Vector2f circle = mousePos.sub(center);
                        float length = Math.abs(end.x - begin.x);
                        float width = Math.abs(end.y - begin.y);
                        float radian = circle.angle();
                        circle = center.add(
                                new Vector2f(
                                        (float) (length / 2 * Math.cos(radian)),
                                        (float) (width / 2 * Math.sin(radian))));
                        if (center.similar(mousePos,EPSILON)) {
                            return center;
                        } else if (circle.similar(mousePos, EPSILON)) {
                            return circle;
                        } else {
                            return null;
                        }
                    }
                });
            }
        });
        strategy.put("fill polygon strategy",
                new FillPolygonStrategy(this, keyboard, mouse, elements));
        strategy.put("string strategy",
                new StringStrategy(this, keyboard, mouse, elements));
        currentStrategy = strategy.get("null strategy");
    }

    @Override
    protected void processInput(float delta) {
        super.processInput(delta);
        currentStrategy.processInput();
    }

    protected void renderLastImage(Graphics g) {
        if (lastImage != null) {
            g.drawImage(lastImage, 0, 0, getScreenWidth(), getScreenHeight(),null);
        }
    }

    @Override
    protected void render(Graphics g) {
        super.render(g);
        renderLastImage(g);
        Matrix3x3f view = getViewportTransform();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            element.draw((Graphics2D)g, view);
        }
        if (similar) {
            renderSimilar(g);
        }
    }

    protected void renderSimilar(Graphics g) {
        Matrix3x3f view = getViewportTransform();
        Vector2f mousePos = getWorldMousePosition();
        ArrayList<ImageElement> elementsTmp = new ArrayList<>(elements);
        for (ImageElement element : elementsTmp) {
            if (element instanceof Similarable) {
                Vector2f tmpPos = ((Similarable) element).similar(mousePos);
                if (tmpPos != null && tmpPos.sub(mousePos).len() > Vector2f.EPSILON) {
                    Utility.fillSimilarCircle((Graphics2D) g, tmpPos, view);
                    if (mouse.buttonDownOnce(MouseEvent.BUTTON2)) {
                        currentStrategy.similar(tmpPos, getViewportTransform());
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        launchApp(new Editor());
    }
}

更多:

第一篇
第二篇
第三篇
第四篇
第五篇
第六篇
第七篇
源代码

猜你喜欢

转载自blog.csdn.net/qq_39384184/article/details/80595082