blob: 15b4138d3c67bc10618a58c208c3a61daac6ac3d [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)
58 self.goto(x1, y1)
59
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
109 item = self._canvas.create_text(x, y,
110 text=str(arg), anchor="sw",
111 fill=self._color)
112 self._items.append(item)
113 if move:
114 x0, y0, x1, y1 = self._canvas.bbox(item)
115 x1 = x1-1 # correction -- calibrated for Windows
116 self.goto(x1, y1)
117
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:
190 x1, y1 = args[0]
191 except:
192 raise Error, "bad point argument: %s" % `args[0]`
193 else:
194 try:
195 x1, y1 = args
196 except:
197 raise Error, "bad coordinates: %s" % `args[0]`
198 x0, y0 = start = self._position
199 self._position = map(float, (x1, y1))
200 if self._filling:
201 self._path.append(self._position)
202 if self._drawing:
203 if self._tracing:
204 dx = float(x1 - x0)
205 dy = float(y1 - y0)
206 distance = hypot(dx, dy)
207 nhops = int(distance)
208 item = self._canvas.create_line(x0, y0, x0, y0,
209 width=self._width,
210 arrow="last",
211 capstyle="round",
212 fill=self._color)
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000213 try:
214 for i in range(1, 1+nhops):
215 x, y = x0 + dx*i/nhops, y0 + dy*i/nhops
216 self._canvas.coords(item, x0, y0, x, y)
217 self._canvas.update()
218 self._canvas.after(10)
219 self._canvas.itemconfigure(item, arrow="none")
220 except Tk.TclError:
221 # Probably the window was closed!
222 return
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000223 else:
224 item = self._canvas.create_line(x0, y0, x1, y1,
225 width=self._width,
226 capstyle="round",
227 fill=self._color)
228 self._items.append(item)
229
230
231_root = None
232_canvas = None
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000233_pen = None
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000234
235class Pen(RawPen):
236
237 def __init__(self):
238 global _root, _canvas
239 if _root is None:
240 _root = Tk.Tk()
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000241 _root.wm_protocol("WM_DELETE_WINDOW", self.destroy)
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000242 if _canvas is None:
243 # XXX Should have scroll bars
244 _canvas = Tk.Canvas(_root, background="white")
245 _canvas.pack(expand=1, fill="both")
246 RawPen.__init__(self, _canvas)
247
Guido van Rossuma96c2d41998-10-24 14:03:48 +0000248 def destroy(self):
249 global _root, _canvas, _pen
250 self.clear()
251 if self is _pen:
252 _pen = None
253 root = _root; _root = None
254 canvas = _canvas; _canvas = None
255 if root:
256 try:
257 root.destroy()
258 except Tk.TclError:
259 pass
Guido van Rossum3fbe67a1998-10-24 05:46:37 +0000260
261def _getpen():
262 global _pen
263 pen = _pen
264 if not pen:
265 _pen = pen = Pen()
266 return pen
267
268def degrees(): _getpen().degrees()
269def radians(): _getpen().radians()
270def reset(): _getpen().reset()
271def clear(): _getpen().clear()
272def tracer(flag): _getpen().tracer(flag)
273def forward(distance): _getpen().forward(distance)
274def backward(distance): _getpen().backward(distance)
275def left(angle): _getpen().left(angle)
276def right(angle): _getpen().right(angle)
277def up(): _getpen().up()
278def down(): _getpen().down()
279def width(width): _getpen().width(width)
280def color(*args): apply(_getpen().color, args)
281def write(arg, move=0): _getpen().write(arg, move)
282def fill(flag): _getpen().fill(flag)
283def circle(radius, extent=None): _getpen().circle(radius, extent)
284def goto(*args): apply(_getpen().goto, args)
285
286def demo():
287 reset()
288 tracer(1)
289 up()
290 backward(100)
291 down()
292 # draw 3 squares; the last filled
293 width(3)
294 for i in range(3):
295 if i == 2:
296 fill(1)
297 for j in range(4):
298 forward(20)
299 left(90)
300 if i == 2:
301 color("maroon")
302 fill(0)
303 up()
304 forward(30)
305 down()
306 width(1)
307 color("black")
308 # move out of the way
309 tracer(0)
310 up()
311 right(90)
312 forward(100)
313 right(90)
314 forward(100)
315 right(180)
316 down()
317 # some text
318 write("startstart", 1)
319 write("start", 1)
320 color("red")
321 # staircase
322 for i in range(5):
323 forward(20)
324 left(90)
325 forward(20)
326 right(90)
327 # filled staircase
328 fill(1)
329 for i in range(5):
330 forward(20)
331 left(90)
332 forward(20)
333 right(90)
334 fill(0)
335 # more text
336 write("end")
337 if __name__ == '__main__':
338 root.mainloop()
339
340if __name__ == '__main__':
341 demo()