My application is meant to draw a tree using L-Systems. I start with an axiom, provide a rule for what should replace what in the next generation. I use a combination of JFrame/JPanel for one button (maybe more in the future)/JComponent for the drawing area. I coded a little turtle graphics method (go forward, turn right, turn left, push current transform, pop a transform). Each time I click the "Generate" button, I call repaint() which in turn calls turtleDraw().
package com.flak;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
@SuppressWarnings("serial")
public class LSystemTree extends JFrame{
JButton generateBut;
int currentAction = 1;
public static void main(String[] args) {
new LSystemTree();
}
public LSystemTree() {
this.setSize(600, 600);
this.setTitle("L-System Tree");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setResizable(false);
JPanel buttonPanel = new JPanel();
Box box = Box.createHorizontalBox();
generateBut = makeButton("Generate", 1);
box.add(generateBut);
buttonPanel.add(box);
Map<String, String> rules = new HashMap<>();
rules.put("F", "FF+[+F-F-F]-[-F+F+F]");
this.add(buttonPanel, BorderLayout.NORTH);
this.add(new TreeDrawing("F", 22.5, rules), BorderLayout.CENTER);
this.setVisible(true);
}
public JButton makeButton(String text, final int actionNum) {
JButton theBut = new JButton();
theBut.setText(text);
theBut.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
currentAction = actionNum;
System.out.println("actionNum: " + actionNum);
repaint();
}
});
return theBut;
}
private class TreeDrawing extends JComponent{
private String axiom;
private String sentence;
private double angle;
private Map<String, String> rules;
private int len;
private Stack<AffineTransform> transformStack;
public TreeDrawing(String axiom, double angle, Map<String, String> rules) {
this.axiom = axiom;
this.sentence = axiom;
this.angle = Math.toRadians(angle);
this.rules = rules;
len = 100;
transformStack = new Stack<>();
}
public void generate() {
len /= 2;
String nextSentence = "";
for(int i = 0; i < sentence.length(); i++) {
char current = sentence.charAt(i);
boolean found = false;
if(rules.containsKey(String.valueOf(current))) {
found = true;
nextSentence += rules.get(String.valueOf(current));
}
if(!found) {
nextSentence += current;
}
}
sentence = nextSentence;
}
private void turtleDraw(Graphics2D g2d) {
g2d.translate(getWidth() / 2, getHeight());
for(int i = 0; i < sentence.length(); i++) {
char current = sentence.charAt(i);
if(current == 'F') {
g2d.drawLine(0, 0, 0, -len);
g2d.translate(0, -len);
} else if(current == '+') {
g2d.rotate(angle);
} else if(current == '-') {
g2d.rotate(-angle);
} else if(current == '[') {
transformStack.push(g2d.getTransform());
} else if(current == ']') {
g2d.setTransform(transformStack.pop());
}
}
generate();
System.out.println(sentence);
}
@Override
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
turtleDraw(g2d);
g2d.dispose();
}
}
}
The first problem I encountered is that paint() which in turn calls paintComponent() (I think) is already called twice when I run the application. That's already annoying. I set setResizable() to false because that would repaint the window again. Is there a way to go around this, as to draw the lines only when I click "Generate"? From what I found, there's no actual way to stop paint() from being called when the application "needs to do it", so maybe there's another way to tweak the code.