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

主程序

以上所有的铺垫工作都已经完毕,接下来主程序的绘制异常简单,如果有不明白的地方可以在返回之前的示例中查看。实际上,最后的测试阶段会发现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方法的菜单操作中只是调用了执行操作的方法,而没有对具体的文件操作做具体实现,下面是对这些方法的详解。

  • 菜单操作方法一览表
修饰符返回值函数名参数说明
protectedbooleanempty()判断当前画板是否为空
protectedbooleansaved()判断当前图像是否保存
protectedvoidsave()保存当前绘制图像
protectedvoidonNew()新建画板并还原默认设置
protectedvoidonOpen()新建画板并打开指定图像
  • 菜单操作方法源代码
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());
    }
}

更多:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值