package logo.ui; import java.awt.Panel; import java.awt.Image; import java.awt.Graphics; import java.awt.Color; import java.awt.Dimension; import java.lang.Thread; import java.util.Vector; /* TurtleGraphicsPanel is the heart of rLogo's graphics. It uses simple geometry to draw lines based on the current angle and position of the turtle. Notice that all drawing commands are queued instead of being immediately executed, and that drawing is first done to an offScreenImage, to reduce screen flicker. */ public class TurtleGraphicsPanel extends Panel { private Image offScreenImage; //"standard" way to avoid screen flicker private Graphics offScreenGraphics; private Dimension offScreenSize; private boolean repaint=true; //determines whether rLogo repaints are automatic private DrawQueue drawQueue = new DrawQueue(); //Drawing commands are placed in a queue private float curX,curY; private boolean penIsDown = true; private boolean turtleIsShowing = false; private int theta; //the angle at which the turtle points private Color backColor = Color.white; private Color curColor = Color.black; public final void setRepaint(boolean b) { repaint=b; } public TurtleGraphicsPanel() { home(); showTurtle(); } public final void penUp() { penIsDown = false; } public final void penDown() { penIsDown = true; } public final void showTurtle() { turtleIsShowing = true; if (repaint) repaint(); } public final void hideTurtle() { turtleIsShowing = false; if (repaint) repaint(); } public final int getTheta() { return theta; } public final void setColor(Color c) { curColor = c; } public final void setTheta(int angle) { theta = angle; } public final void turnRight(int angle) { setTheta(getTheta()+angle); if (repaint) repaint(); } public final void turnLeft(int angle) { setTheta(getTheta()-angle); if (repaint) repaint(); } public final void backward(int distance) { forward(-1*distance); } public final void forward(int distance) { //using floats improves the accuracy of the drawing a bit. float newX=curX; float newY=curY; //calculate the new position: newX+=distance*Math.cos(Math.PI*theta/180.0); newY+=distance*Math.sin(Math.PI*theta/180.0); //draw a line, if applicable: if (penIsDown) { drawQueue.line((int)Math.round(curX),(int)Math.round(curY),(int)Math.round(newX),(int)Math.round(newY),curColor); } //update the current position: curX = newX; curY = newY; if (repaint) repaint(); } private final void drawTurtle(Graphics g) { int baseX1=(int)(7*Math.cos(Math.PI*(getTheta()+90)/180.0)); int baseY1=(int)(7*Math.sin(Math.PI*(getTheta()+90)/180.0)); int baseX2=-1*baseX1; int baseY2=-1*baseY1; int tipX=(int)(9*Math.cos(Math.PI*getTheta()/180.0)); int tipY=(int)(9*Math.sin(Math.PI*getTheta()/180.0)); g.setColor(Color.blue); g.translate((int)curX,(int)curY); g.drawLine(baseX1,baseY1,baseX2,baseY2); g.drawLine(baseX1,baseY1,tipX,tipY); g.drawLine(baseX2,baseY2,tipX,tipY); } /** Outputs text at the current turtle position. */ public final void drawString(String s) { drawQueue.drawString(s,curColor,(int)curX,(int)curY); if (repaint) repaint(); } public final void home() { setTheta(0); curX=0; curY=0; if (repaint) repaint(); } public final void setBackColor(Color c) { backColor=c; drawQueue.setBackColor(backColor); if (repaint) repaint(); } public final void clearScreen() { drawQueue.clearScreen(backColor); if (repaint) repaint(); } public final void paint(Graphics g) { repaint(); //repaints the screen whenever another window obliterates it. } public final synchronized void update(Graphics g) { //first, use the standard "flicker-free" code; i.e. all drawing //is performed on an offscreen buffer, then quickly dumped to //the screen. Notice that the DrawQueue object is used to perform //the actual drawing, and if a resize has occurred, it does a //full redraw: Dimension d = size(); if((offScreenImage == null) || (d.width != offScreenSize.width) || (d.height != offScreenSize.height)) { offScreenImage = createImage(d.width, d.height); offScreenSize = d; offScreenGraphics = offScreenImage.getGraphics(); offScreenGraphics.translate(d.width/2,d.height/2); drawQueue.redraw(offScreenGraphics,d); //redraw everything whenever offScreenImage changes. } drawQueue.draw(offScreenGraphics,d); //Offscreen drawing is finished, now dump to the screen: g.drawImage(offScreenImage, 0, 0, null); //Next, the turtle is drawn *on top of* the screen. This way, //there is no need to explicitly erase it later: since it isn't //on the offScreenImage, it won't be redrawn the next time. if (turtleIsShowing) { g.translate(size().width/2,size().height/2); drawTurtle(g); } } } /* Drawing commands are placed in a queue, instead of being executed immediately. This facilitates full control over screen refreshes (several commands may be queued before a redraw is issued, which permits dramatic visual effects) and redraws, such as when the user changes the background color, or when they resize the TurtleGraphicsPanel. Also, this will permit a future "undo" command. */ class DrawQueue extends Vector { private int nextCommand = 0; //keeps track of which commands have been executed public final void draw(Graphics g, Dimension size) { //draws pending commands DrawCommand d; while ( nextCommand0) { DrawCommand firstCommand = (DrawCommand)elementAt(0); removeElementAt(nextCommand); if ( firstCommand.getType()==DrawCommand.SET_BACK_COLOR ) { setElementAt(d,0); //replace existing command } else { insertElementAt(d,0); } nextCommand = 0; break; } //fall through to the default behavior. default: d.draw(g,size); nextCommand++; } } } public final void redraw(Graphics g, Dimension size) { //redraw everything in the queue nextCommand = 0; draw(g,size); } public final void line(int x1, int y1, int x2, int y2,Color c) { DrawCommand d = new DrawCommand(DrawCommand.LINE); d.setStart(x1,y1); d.setStop(x2,y2); d.setColor(c); addElement(d); } public final void clearScreen(Color c) { DrawCommand d = new DrawCommand(DrawCommand.CLEAR_SCREEN); d.setColor(c); addElement(d); } public final void setBackColor(Color c) { DrawCommand d = new DrawCommand(DrawCommand.SET_BACK_COLOR); d.setColor(c); addElement(d); } public final void drawString(String s, Color c, int x, int y) { DrawCommand d = new DrawCommand(DrawCommand.DRAW_STRING); d.setColor(c); d.setStart(x,y); d.setString(s); addElement(d); } } /* Drawing commands are implemented in the DrawCommand class. This class stores a drawing command. Future calls to draw() will performs it on a Graphics object. */ class DrawCommand { public static final int LINE = 0; public static final int CLEAR_SCREEN = 1; public static final int SET_BACK_COLOR = 2; public static final int DRAW_STRING = 3; private String s; private int x1,y1,x2,y2; private Color c; private int type; public DrawCommand(int TYPE) { type = TYPE; } public final int getType() { return type; } public final void setStart(int x, int y) { x1=x; y1=y; } public final void setStop(int x, int y) { x2=x; y2=y; } public final void setColor(Color C) { c=C; } public final void setString(String S) { s=S; } public final void draw(Graphics g,Dimension d) { switch ( type ) { case LINE : g.setColor(c); g.drawLine(x1,y1,x2,y2); break; case CLEAR_SCREEN : case SET_BACK_COLOR : g.setColor(c); g.fillRect(-d.width/2,-d.height/2,d.width,d.height); break; case DRAW_STRING : g.setColor(c); if (s!=null) g.drawString(s,x1,y1); break; } } }