blob: 49375a7258bf170844bab4e791e047d18f884da2 [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
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 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)
88 except Tk.TclError:
89 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
111 item = self._canvas.create_text(x, y,
112 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)
224 self._canvas.itemconfigure(item, arrow="none")
225 except Tk.TclError:
226 # Probably the window was closed!
227 return
228 else:
229 item = self._canvas.create_line(x0, y0, x1, y1,
230 width=self._width,
231 capstyle="round",
232 fill=self._color)
233 self._items.append(item)
234
235
236_root = None
237_canvas = None
238_pen = None
239
240class Pen(RawPen):
241
242 def __init__(self):
243 global _root, _canvas
244 if _root is None:
245 _root = Tk.Tk()
246 _root.wm_protocol("WM_DELETE_WINDOW", self._destroy)
247 if _canvas is None:
248 # XXX Should have scroll bars
249 _canvas = Tk.Canvas(_root, background="white")
250 _canvas.pack(expand=1, fill="both")
251 RawPen.__init__(self, _canvas)
252
253 def _destroy(self):
254 global _root, _canvas, _pen
255 root = self._canvas._root()
256 if root is _root:
257 _pen = None
258 _root = None
259 _canvas = None
260 root.destroy()
261
262
263def _getpen():
264 global _pen
265 pen = _pen
266 if not pen:
267 _pen = pen = Pen()
268 return pen
269
270def degrees(): _getpen().degrees()
271def radians(): _getpen().radians()
272def reset(): _getpen().reset()
273def clear(): _getpen().clear()
274def tracer(flag): _getpen().tracer(flag)
275def forward(distance): _getpen().forward(distance)
276def backward(distance): _getpen().backward(distance)
277def left(angle): _getpen().left(angle)
278def right(angle): _getpen().right(angle)
279def up(): _getpen().up()
280def down(): _getpen().down()
281def width(width): _getpen().width(width)
282def color(*args): apply(_getpen().color, args)
283def write(arg, move=0): _getpen().write(arg, move)
284def fill(flag): _getpen().fill(flag)
285def circle(radius, extent=None): _getpen().circle(radius, extent)
286def goto(*args): apply(_getpen().goto, args)
287
288def demo():
289 reset()
290 tracer(1)
291 up()
292 backward(100)
293 down()
294 # draw 3 squares; the last filled
295 width(3)
296 for i in range(3):
297 if i == 2:
298 fill(1)
299 for j in range(4):
300 forward(20)
301 left(90)
302 if i == 2:
303 color("maroon")
304 fill(0)
305 up()
306 forward(30)
307 down()
308 width(1)
309 color("black")
310 # move out of the way
311 tracer(0)
312 up()
313 right(90)
314 forward(100)
315 right(90)
316 forward(100)
317 right(180)
318 down()
319 # some text
320 write("startstart", 1)
321 write("start", 1)
322 color("red")
323 # staircase
324 for i in range(5):
325 forward(20)
326 left(90)
327 forward(20)
328 right(90)
329 # filled staircase
330 fill(1)
331 for i in range(5):
332 forward(20)
333 left(90)
334 forward(20)
335 right(90)
336 fill(0)
337 # more text
338 write("end")
339 if __name__ == '__main__':
340 _root.mainloop()
341
342if __name__ == '__main__':
343 demo()