blob: dde5725ffb188a9f6e60d3b3a37f82371bfc9277 [file] [log] [blame]
Guido van Rossumb241b671998-12-04 16:42:46 +00001# LogoMation-like turtle graphics
2
3from math import * # Also for export
4import Tkinter
Martin v. Löwis4b6ea792000-10-01 17:52:01 +00005class Error(Exception):
6 pass
Guido van Rossumb241b671998-12-04 16:42:46 +00007
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 canvas._root().tkraise()
43
44 def clear(self):
45 self.fill(0)
46 canvas = self._canvas
47 items = self._items
48 self._items = []
49 for item in items:
50 canvas.delete(item)
51
52 def tracer(self, flag):
53 self._tracing = flag
54
55 def forward(self, distance):
56 x0, y0 = start = self._position
57 x1 = x0 + distance * cos(self._angle*self._invradian)
58 y1 = y0 - distance * sin(self._angle*self._invradian)
59 self._goto(x1, y1)
60
61 def backward(self, distance):
62 self.forward(-distance)
63
64 def left(self, angle):
65 self._angle = (self._angle + angle) % self._fullcircle
66
67 def right(self, angle):
68 self.left(-angle)
69
70 def up(self):
71 self._drawing = 0
72
73 def down(self):
74 self._drawing = 1
75
76 def width(self, width):
77 self._width = float(width)
78
79 def color(self, *args):
80 if not args:
81 raise Error, "no color arguments"
82 if len(args) == 1:
83 color = args[0]
84 if type(color) == type(""):
85 # Test the color first
86 try:
87 id = self._canvas.create_line(0, 0, 0, 0, fill=color)
Martin v. Löwis4b6ea792000-10-01 17:52:01 +000088 except Tkinter.TclError:
Guido van Rossumb241b671998-12-04 16:42:46 +000089 raise Error, "bad color string: %s" % `color`
90 self._color = color
91 return
92 try:
93 r, g, b = color
94 except:
95 raise Error, "bad color sequence: %s" % `color`
96 else:
97 try:
98 r, g, b = args
99 except:
100 raise Error, "bad color arguments: %s" % `args`
101 assert 0 <= r <= 1
102 assert 0 <= g <= 1
103 assert 0 <= b <= 1
104 x = 255.0
105 y = 0.5
106 self._color = "#%02x%02x%02x" % (int(r*x+y), int(g*x+y), int(b*x+y))
107
108 def write(self, arg, move=0):
109 x, y = start = self._position
110 x = x-1 # correction -- calibrated for Windows
Fred Draked038ca82000-10-23 18:31:14 +0000111 item = self._canvas.create_text(x, y,
Guido van Rossumb241b671998-12-04 16:42:46 +0000112 text=str(arg), anchor="sw",
113 fill=self._color)
114 self._items.append(item)
115 if move:
116 x0, y0, x1, y1 = self._canvas.bbox(item)
117 self._goto(x1, y1)
118
119 def fill(self, flag):
120 if self._filling:
121 path = tuple(self._path)
122 smooth = self._filling < 0
123 if len(path) > 2:
124 item = self._canvas._create('polygon', path,
125 {'fill': self._color,
126 'smooth': smooth})
127 self._items.append(item)
128 self._canvas.lower(item)
129 if self._tofill:
130 for item in self._tofill:
131 self._canvas.itemconfigure(item, fill=self._color)
132 self._items.append(item)
133 self._path = []
134 self._tofill = []
135 self._filling = flag
136 if flag:
137 self._path.append(self._position)
138
139 def circle(self, radius, extent=None):
140 if extent is None:
141 extent = self._fullcircle
142 x0, y0 = self._position
143 xc = x0 - radius * sin(self._angle * self._invradian)
144 yc = y0 - radius * cos(self._angle * self._invradian)
145 if radius >= 0.0:
146 start = self._angle - 90.0
147 else:
148 start = self._angle + 90.0
149 extent = -extent
150 if self._filling:
151 if abs(extent) >= self._fullcircle:
152 item = self._canvas.create_oval(xc-radius, yc-radius,
153 xc+radius, yc+radius,
154 width=self._width,
155 outline="")
156 self._tofill.append(item)
157 item = self._canvas.create_arc(xc-radius, yc-radius,
158 xc+radius, yc+radius,
159 style="chord",
160 start=start,
161 extent=extent,
162 width=self._width,
163 outline="")
164 self._tofill.append(item)
165 if self._drawing:
166 if abs(extent) >= self._fullcircle:
167 item = self._canvas.create_oval(xc-radius, yc-radius,
168 xc+radius, yc+radius,
169 width=self._width,
170 outline=self._color)
171 self._items.append(item)
172 item = self._canvas.create_arc(xc-radius, yc-radius,
173 xc+radius, yc+radius,
174 style="arc",
175 start=start,
176 extent=extent,
177 width=self._width,
178 outline=self._color)
179 self._items.append(item)
180 angle = start + extent
181 x1 = xc + abs(radius) * cos(angle * self._invradian)
182 y1 = yc - abs(radius) * sin(angle * self._invradian)
183 self._angle = (self._angle + extent) % self._fullcircle
184 self._position = x1, y1
185 if self._filling:
186 self._path.append(self._position)
187
188 def goto(self, *args):
189 if len(args) == 1:
190 try:
191 x, y = args[0]
192 except:
193 raise Error, "bad point argument: %s" % `args[0]`
194 else:
195 try:
196 x, y = args
197 except:
198 raise Error, "bad coordinates: %s" % `args[0]`
199 x0, y0 = self._origin
200 self._goto(x0+x, y0-y)
201
202 def _goto(self, x1, y1):
203 x0, y0 = start = self._position
204 self._position = map(float, (x1, y1))
205 if self._filling:
206 self._path.append(self._position)
207 if self._drawing:
208 if self._tracing:
209 dx = float(x1 - x0)
210 dy = float(y1 - y0)
211 distance = hypot(dx, dy)
212 nhops = int(distance)
213 item = self._canvas.create_line(x0, y0, x0, y0,
214 width=self._width,
215 arrow="last",
216 capstyle="round",
217 fill=self._color)
218 try:
219 for i in range(1, 1+nhops):
220 x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
221 self._canvas.coords(item, x0, y0, x, y)
222 self._canvas.update()
223 self._canvas.after(10)
Guido van Rossuma659efe2001-01-01 19:11:07 +0000224 # in case nhops==0
225 self._canvas.coords(item, x0, y0, x1, y1)
Guido van Rossumb241b671998-12-04 16:42:46 +0000226 self._canvas.itemconfigure(item, arrow="none")
Martin v. Löwis4b6ea792000-10-01 17:52:01 +0000227 except Tkinter.TclError:
Guido van Rossumb241b671998-12-04 16:42:46 +0000228 # Probably the window was closed!
229 return
230 else:
231 item = self._canvas.create_line(x0, y0, x1, y1,
232 width=self._width,
233 capstyle="round",
234 fill=self._color)
235 self._items.append(item)
236
237
238_root = None
239_canvas = None
240_pen = None
241
242class Pen(RawPen):
243
244 def __init__(self):
245 global _root, _canvas
246 if _root is None:
Martin v. Löwis4b6ea792000-10-01 17:52:01 +0000247 _root = Tkinter.Tk()
Guido van Rossumb241b671998-12-04 16:42:46 +0000248 _root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
249 if _canvas is None:
250 # XXX Should have scroll bars
Martin v. Löwis4b6ea792000-10-01 17:52:01 +0000251 _canvas = Tkinter.Canvas(_root, background="white")
Guido van Rossumb241b671998-12-04 16:42:46 +0000252 _canvas.pack(expand=1, fill="both")
253 RawPen.__init__(self, _canvas)
254
255 def _destroy(self):
256 global _root, _canvas, _pen
257 root = self._canvas._root()
258 if root is _root:
259 _pen = None
260 _root = None
261 _canvas = None
262 root.destroy()
Fred Draked038ca82000-10-23 18:31:14 +0000263
Guido van Rossumb241b671998-12-04 16:42:46 +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()