### MSH Logo – A GUI Disaster

Wed, Feb 8, 2006 4-minute read

Ok, so now that we’ve talked about our grand design for MSH Logo, our next task is to simply integrate this into a GUI.  You can download the Visual Studio 2005 project from here.

The most interesting class, by far, is our Turtle class:

``````using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;

{
/// <summary>
/// A turtle class that implements some of the logo primitives.
/// It stores a reference to the canvas upon which it draws, and
/// is responsible for drawing on that canvas.
/// </summary>
class Turtle
{
Graphics canvas;
Pen drawingPen = new Pen(Color.LightGreen);

// Although the canvas can only represent integer
// positions, we store our state in double precision.
// Otherwise, most interesting graphics (that tend to
// involve recursion and small numbers) look terribly
// broken.
double currentX, currentY;
bool penDown = true;
bool showTurtle = true;
int direction = 90;

public Turtle(Graphics canvas)
{
this.canvas = canvas;

Initialize();
}

public void PenUp()
{
penDown = false;
}

public void PenDown()
{
penDown = true;
}

public void Forward(double steps)
{
int oldX = (int) currentX;
int oldY = (int) currentY;

// In essense, the turtle draws the hypotenuse
// of a triangle as it moves.  Since the user provides
// the length of the hypotenuse, we use standard
// trigonometry to determine the X and Y components
// of the movement independently.

if(penDown)
{
canvas.DrawLine(drawingPen, oldX, oldY,
(int) currentX, (int) currentY);
}
}

public void Backward(double steps)
{
Forward(-1 * steps);
}

public void Left(int degrees)
{
direction = (direction + degrees) % 360;
}

public void Right(int degrees)
{
direction = (direction - degrees + 360) % 360;
}

public void Hide()
{
showTurtle = false;
}

public void Show()
{
showTurtle = true;
}

public void Draw()
{
if (showTurtle)
{
// We leverage the 2d transformations of the .Net
// Graphics class here to save us from doing the
// math for rotation contortions ourselves.
// Rather than draw a rotated turtle, we instead rotate
// (and reposition) the canvas, then draw a straight
// turtle.  When we restore the canvas again, the
// turtle now appears rotated.

System.Drawing.Drawing2D.GraphicsState canvasState =
canvas.Save();
canvas.TranslateTransform((float) currentX, (float) currentY);
canvas.RotateTransform(90 - direction);

canvas.DrawLine(drawingPen, -4, 4, 0, -8);
canvas.DrawLine(drawingPen, 0, -8, 4, 4);
canvas.DrawLine(drawingPen, -4, 4, 4, 4);

canvas.Restore(canvasState);
}
}

public void Reset()
{
Initialize();
canvas.Clear(Color.DarkGreen);
}

private void Initialize()
{
currentX = canvas.VisibleClipBounds.Width / 2.0;
currentY = canvas.VisibleClipBounds.Height / 2.0;

penDown = true;
showTurtle = true;
direction = 90;
}

// The user specifies their angles in degrees, but
// the .Net math classes prefer radians.
{
return (Math.PI * (double) degrees / 180.0);
}
}
}
``````

Our GUI application mainly interacts with the Turtle object:

``````public partial class MonadHost : Form
{
// The drawing surface is the canvas on which the
// turtle draws.
private Graphics drawingSurface = null;
private Turtle turtle = null;

// We save the image of the turtle's tracks just
// before we draw the turtle icon.  That way, when
// the turtle moves, we don't have to worry about erasing
// the icon.
Image savedImage = null;

{
InitializeComponent();
InitializeCustom();
}

private void InitializeCustom()
{
InitializeCanvas();

this.tabControl.Focus();
}

// Make our form look a little more presentable when
// we resize it.
private void MonadHost_ResizeEnd(object sender, EventArgs e)
{
InitializeCanvas();
}

// This brings our application back to a clean state.
// We create a fresh new canvas the same size as the current
// form, create a new turtle to reference that canvas,
// and draw the turtle.
private void InitializeCanvas()
{
this.pictureBox.Image =
new Bitmap(pictureBox.Width, pictureBox.Height);
drawingSurface = Graphics.FromImage(this.pictureBox.Image);
drawingSurface.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.HighQuality;

turtle = new Turtle(drawingSurface);
turtle.Reset();

savedImage = new Bitmap(this.pictureBox.Image);
turtle.Draw();
}

// The following methods are pretty similar.  We
// first draw our saved image to the canvas (the one without
// the turtle icon,) have the turtle draw (or do) whatever
// it was told to do, save the resulting image, draw
// the turtle icon, and finally refresh the view of
// the canvas.
{
drawingSurface.DrawImage(savedImage, 0, 0);

turtle.PenUp();

savedImage = new Bitmap(this.pictureBox.Image);
turtle.Draw();
this.pictureBox.Refresh();
}

(...)

{
drawingSurface.DrawImage(savedImage, 0, 0);

turtle.Backward(10);

savedImage = new Bitmap(this.pictureBox.Image);
turtle.Draw();
this.pictureBox.Refresh();
}
}
}
``````

When you run the program, its user interface may feel a little barbaric.  Or (more probably,) a lot barbaric.  The feature set is closed, and offers no extensibility.  In fact, you may already be contemplating a lawsuit against me for an acute case of carpal tunnel syndrome.

Next time, we’ll look at a way to resolve this issue.

[Edit: Monad has now been renamed to Windows PowerShell. This script or discussion may require slight adjustments before it applies directly to newer builds.]