blob: 9aa0835568543dc064bd6d63d1bb9240f2c984fd [file] [log] [blame]
Guido van Rossum3fbe67a1998-10-24 05:46:37 +00001# LogoMation-like turtle graphics
2
3from math import * # Also for export
4import Tkinter
5Tk = Tkinter
6Error = Exception
7
8class RawPen:
9
10 def __init__(self, canvas):
11 self._canvas = canvas
12 self._items = []
13 self._tracing = 1
14 self.degrees()
15 self.reset()
16
17 def degrees(self, fullcircle=360.0):
18 self._fullcircle = fullcircle
19 self._invradian = pi / (fullcircle * 0.5)
20
21 def radians(self):
22 self.degrees(2.0*pi)
23
24 def reset(self):
25 canvas = self._canvas
26 width = canvas.winfo_width()
27 height = canvas.winfo_height()
28 if width <= 1:
29 width = canvas['width']
30 if height <= 1:
31 height = canvas['height']
32 self._origin = float(width)/2.0, float(height)/2.0
33 self._position = self._origin
34 self._angle = 0.0
35 self._drawing = 1
36 self._width = 1
37 self._color = "black"
38 self._filling = 0
39 self._path = []
40 self._tofill = []
41 self.clear()
42
43 def clear(self):
44 self.fill(0)
45 canvas = self._canvas
46 items = self._items
47 self._items = []
48 for item in items:
49 canvas.delete(item)
50
51 def tracer(self, flag):
52 self._tracing = flag
53
54 def forward(self, distance):
55 x0, y0 = start = self._position
56 x1 = x0 + distance * cos(self._angle*self._invradian)
57 y1 = y0 - distance * sin(self._angle*self._invradian)
Guido van Rossum5ac15bc1998-10-24 16:29:21 +000058 self._goto(x1, y1)
Guido van Rossum3fbe67a1998-10-24 05:46:37 +000059
60 def backward(self, distance):
61 self.forward(-distance)
62
63 def left(self, angle):
64 self._angle = (self._angle + angle) % self._fullcircle
65
66 def right(self, angle):
67 self.left(-angle)
68
69 def up(self):
70 self._drawing = 0
71
72 def down(self):
73 self._drawing = 1
74
75 def width(self, width):
76 self._width = float(width)
77
78 def color(self, *args):
79 if not args:
80 raise Error, "no color arguments"
81 if len(args) == 1:
82 color = args[0]
83 if type(color) == type(""):
84 # Test the color first
85 try:
86 id = self._canvas.create_line(0, 0, 0, 0, fill=color)
87 except Tk.TclError:
88 raise Error, "bad color string: %s" % `color`
89 self._color = color
90 return
91 try:
92 r, g, b = color
93 except:
94 raise Error, "bad color sequence: %s" % `color`
95 else:
96 try:
97 r, g, b = args
98 except:
99 raise Error, "bad color arguments: %s" % `args`
100 assert 0 <= r <= 1
101 assert 0 <= g <= 1
102 assert 0 <= b <= 1
103 x = 255.0
104 y = 0.5
105 self._color = "#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y))
106
107 def write(self, arg, move=0):
108 x, y = start = self._position
Guido van Rossum5ac15bc1998-10-24 16:29:21 +0000109 x = x-1 # correction -- calibrated for Windows
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000110 item = self._canvas.create_text(x, y,
111 text=str(arg), anchor="sw",
112 fill=self._color)
113 self._items.append(item)
114 if move:
115 x0, y0, x1, y1 = self._canvas.bbox(item)
Guido van Rossum5ac15bc1998-10-24 16:29:21 +0000116 self._goto(x1, y1)
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000117
118 def fill(self, flag):
119 if self._filling:
120 path = tuple(self._path)
121 smooth = self._filling < 0
122 if len(path) > 2:
123 item = self._canvas._create('polygon', path,
124 {'fill': self._color,
125 'smooth': smooth})
126 self._items.append(item)
127 self._canvas.lower(item)
128 if self._tofill:
129 for item in self._tofill:
130 self._canvas.itemconfigure(item, fill=self._color)
131 self._items.append(item)
132 self._path = []
133 self._tofill = []
134 self._filling = flag
135 if flag:
136 self._path.append(self._position)
137
138 def circle(self, radius, extent=None):
139 if extent is None:
140 extent = self._fullcircle
141 x0, y0 = self._position
142 xc = x0 - radius * sin(self._angle * self._invradian)
143 yc = y0 - radius * cos(self._angle * self._invradian)
144 if radius >= 0.0:
145 start = self._angle - 90.0
146 else:
147 start = self._angle + 90.0
148 extent = -extent
149 if self._filling:
150 if abs(extent) >= self._fullcircle:
151 item = self._canvas.create_oval(xc-radius, yc-radius,
152 xc+radius, yc+radius,
153 width=self._width,
154 outline="")
155 self._tofill.append(item)
156 item = self._canvas.create_arc(xc-radius, yc-radius,
157 xc+radius, yc+radius,
158 style="chord",
159 start=start,
160 extent=extent,
161 width=self._width,
162 outline="")
163 self._tofill.append(item)
164 if self._drawing:
165 if abs(extent) >= self._fullcircle:
166 item = self._canvas.create_oval(xc-radius, yc-radius,
167 xc+radius, yc+radius,
168 width=self._width,
169 outline=self._color)
170 self._items.append(item)
171 item = self._canvas.create_arc(xc-radius, yc-radius,
172 xc+radius, yc+radius,
173 style="arc",
174 start=start,
175 extent=extent,
176 width=self._width,
177 outline=self._color)
178 self._items.append(item)
179 angle = start + extent
180 x1 = xc + abs(radius) * cos(angle * self._invradian)
181 y1 = yc - abs(radius) * sin(angle * self._invradian)
182 self._angle = (self._angle + extent) % self._fullcircle
183 self._position = x1, y1
184 if self._filling:
185 self._path.append(self._position)
186
187 def goto(self, *args):
188 if len(args) == 1:
189 try:
Guido van Rossum5ac15bc1998-10-24 16:29:21 +0000190 x, y = args[0]
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000191 except:
192 raise Error, "bad point argument: %s" % `args[0]`
193 else:
194 try:
Guido van Rossum5ac15bc1998-10-24 16:29:21 +0000195 x, y = args
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000196 except:
197 raise Error, "bad coordinates: %s" % `args[0]`
Guido van Rossum5ac15bc1998-10-24 16:29:21 +0000198 x0, y0 = self._origin
199 self._goto(x0+x, y0-y)
200
201 def _goto(self, x1, y1):
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000202 x0, y0 = start = self._position
203 self._position = map(float, (x1, y1))
204 if self._filling:
205 self._path.append(self._position)
206 if self._drawing:
207 if self._tracing:
208 dx = float(x1 - x0)
209 dy = float(y1 - y0)
210 distance = hypot(dx, dy)
211 nhops = int(distance)
212 item = self._canvas.create_line(x0, y0, x0, y0,
213 width=self._width,
214 arrow="last",
215 capstyle="round",
216 fill=self._color)
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000217 try:
218 for i in range(1, 1+nhops):
219 x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
220 self._canvas.coords(item, x0, y0, x, y)
221 self._canvas.update()
222 self._canvas.after(10)
223 self._canvas.itemconfigure(item, arrow="none")
224 except Tk.TclError:
225 # Probably the window was closed!
226 return
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000227 else:
228 item = self._canvas.create_line(x0, y0, x1, y1,
229 width=self._width,
230 capstyle="round",
231 fill=self._color)
232 self._items.append(item)
233
234
235_root = None
236_canvas = None
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000237_pen = None
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000238
239class Pen(RawPen):
240
241 def __init__(self):
242 global _root, _canvas
243 if _root is None:
244 _root = Tk.Tk()
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000245 _root.wm_protocol("WM_DELETE_WINDOW", self.destroy)
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000246 if _canvas is None:
247 # XXX Should have scroll bars
248 _canvas = Tk.Canvas(_root, background="white")
249 _canvas.pack(expand=1, fill="both")
250 RawPen.__init__(self, _canvas)
251
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000252 def destroy(self):
253 global _root, _canvas, _pen
254 self.clear()
255 if self is _pen:
256 _pen = None
257 root = _root; _root = None
258 canvas = _canvas; _canvas = None
259 if root:
260 try:
261 root.destroy()
262 except Tk.TclError:
263 pass
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000264
265def _getpen():
266 global _pen
267 pen = _pen
268 if not pen:
269 _pen = pen = Pen()
270 return pen
271
272def degrees(): _getpen().degrees()
273def radians(): _getpen().radians()
274def reset(): _getpen().reset()
275def clear(): _getpen().clear()
276def tracer(flag): _getpen().tracer(flag)
277def forward(distance): _getpen().forward(distance)
278def backward(distance): _getpen().backward(distance)
279def left(angle): _getpen().left(angle)
280def right(angle): _getpen().right(angle)
281def up(): _getpen().up()
282def down(): _getpen().down()
283def width(width): _getpen().width(width)
284def color(*args): apply(_getpen().color, args)
285def write(arg, move=0): _getpen().write(arg, move)
286def fill(flag): _getpen().fill(flag)
287def circle(radius, extent=None): _getpen().circle(radius, extent)
288def goto(*args): apply(_getpen().goto, args)
289
290def demo():
291 reset()
292 tracer(1)
293 up()
294 backward(100)
295 down()
296 # draw 3 squares; the last filled
297 width(3)
298 for i in range(3):
299 if i == 2:
300 fill(1)
301 for j in range(4):
302 forward(20)
303 left(90)
304 if i == 2:
305 color("maroon")
306 fill(0)
307 up()
308 forward(30)
309 down()
310 width(1)
311 color("black")
312 # move out of the way
313 tracer(0)
314 up()
315 right(90)
316 forward(100)
317 right(90)
318 forward(100)
319 right(180)
320 down()
321 # some text
322 write("startstart", 1)
323 write("start", 1)
324 color("red")
325 # staircase
326 for i in range(5):
327 forward(20)
328 left(90)
329 forward(20)
330 right(90)
331 # filled staircase
332 fill(1)
333 for i in range(5):
334 forward(20)
335 left(90)
336 forward(20)
337 right(90)
338 fill(0)
339 # more text
340 write("end")
341 if __name__ == '__main__':
342 root.mainloop()
343
344if __name__ == '__main__':
345 demo()