source: tools/area_editor.py@ 543:80264aee0af2

Last change on this file since 543:80264aee0af2 was 543:80264aee0af2, checked in by Neil Muller <drnlmuller@…>, 9 years ago

Remove debugging print

  • Property exe set to *
File size: 47.9 KB
Line 
1#!/usr/bin/env python
2
3# The basic area editor
4#
5# To edit an existing level, use
6# editor levelname
7#
8# To create a new level:
9#
10# editor levelname <xsize> <ysize>
11# (size specified in pixels
12#
13
14import os
15import sys
16
17import pygame
18import pygame.locals as pgl
19
20sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
21
22import pymunk
23
24from albow.root import RootWidget
25from albow.widget import Widget
26from albow.controls import Button, Label, CheckBox
27from albow.dialogs import alert, Dialog, ask
28from albow.layout import Row
29from albow.fields import TextField
30from albow.table_view import TableView, TableColumn
31
32from nagslang.options import parse_args
33from nagslang.constants import SCREEN
34from nagslang.level import Level, POLY_COLORS, LINE_COLOR
35from nagslang.yamlish import load_s
36import nagslang.enemies as ne
37import nagslang.game_object as ngo
38import nagslang.collectable as collectable
39import nagslang.puzzle as np
40
41# layout constants
42MENU_BUTTON_HEIGHT = 35
43MENU_PAD = 4
44MENU_HALF_PAD = MENU_PAD // 2
45MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
46MENU_WIDTH = 200 - MENU_PAD
47MENU_HALF_WIDTH = MENU_WIDTH // 2 - MENU_HALF_PAD
48
49BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
50HALF_BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_HALF_WIDTH, MENU_BUTTON_HEIGHT)
51CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
52 MENU_BUTTON_HEIGHT // 2)
53
54
55class TestWorld(object):
56
57 def __init__(self):
58 self.level_state = {}
59 self.inventory = {}
60
61
62def distance(tup1, tup2):
63 return (tup1[0] - tup2[0]) ** 2 + (tup1[1] - tup2[1]) ** 2
64
65
66class EditorLevel(Level):
67
68 def __init__(self, name, x=800, y=600):
69 world = TestWorld()
70 super(EditorLevel, self).__init__(name, world)
71 self.x = x
72 self.y = y
73 # Lookup initiliasition info from the objects
74 self.lookup = {}
75 self._move_poly = None
76
77 def _in_bounds(self, pos):
78 if pos[0] < 0 or pos[0] >= self.x:
79 return False
80 if pos[1] < 0 or pos[1] >= self.y:
81 return False
82 return True
83
84 def load(self, space):
85 super(EditorLevel, self).load(space)
86 # Needed to fill in the lookup dict
87 self.reset_objs()
88
89 def point_to_pygame(self, pos):
90 # inverse of point_to_pymunk
91 # (this is also the same as point_to_pymunk, but an additional
92 # function for sanity later in pyweek).
93 return (pos[0], self.y - pos[1])
94
95 def point_to_pymunk(self, pos):
96 # inverse of point_to_pygame
97 # (this is also the same as point_to_pygame, but an additional
98 # function for sanity later in pyweek).
99 return (pos[0], self.y - pos[1])
100
101 def add_point(self, poly_index, pos):
102 self.polygons.setdefault(poly_index, [])
103 if not self._in_bounds(pos):
104 return False
105 if not self.polygons[poly_index]:
106 point = self.point_to_pymunk(pos)
107 self.polygons[poly_index].append(point)
108 else:
109 add_pos = self.point_to_pymunk(pos)
110 self.polygons[poly_index].append(add_pos)
111 return True
112
113 def delete_point(self, index):
114 if index in self.polygons and len(self.polygons[index]) > 0:
115 self.polygons[index].pop()
116
117 def close_poly(self, index):
118 """Attempts to close the current polygon.
119
120 We allow a small additional step to close the polygon, but
121 it's limited as it's a magic point addition"""
122 if len(self.polygons[index]) < 2:
123 # Too small
124 return False
125 first = self.polygons[index][0]
126 if self.add_point(index, self.point_to_pygame(first)):
127 return True
128 return False
129
130 def add_line(self, start_pos, end_pos):
131 startpoint = self.point_to_pymunk(start_pos)
132 endpoint = self.point_to_pymunk(end_pos)
133 self.lines.append([startpoint, endpoint])
134
135 def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos,
136 move_point_mode, move_poly_mode, move_point, zoom_factor,
137 level_widget):
138 self._draw_background(True)
139 # Draw polygons as needed for the editor
140 line_width = int(2 * zoom_factor)
141 if filled:
142 self._draw_exterior(True)
143 for index, polygon in self.polygons.items():
144 color = POLY_COLORS.get(index, pygame.color.THECOLORS['black'])
145 if move_point_mode and index == self._move_poly and move_point:
146 pointlist = [p for p in polygon]
147 pointlist = [self.point_to_pygame(p) if p != move_point else
148 mouse_pos for p in pointlist]
149 pygame.draw.lines(self._surface, color, False, pointlist,
150 line_width)
151 break
152 if move_poly_mode and index == self._move_poly and move_point:
153 new_point = self.point_to_pymunk(mouse_pos)
154 pointlist = [self.point_to_pygame(p)
155 for p in self.translate_poly(
156 polygon, move_point, new_point)]
157 pygame.draw.lines(self._surface, color, False, pointlist,
158 line_width)
159 #break
160 if len(polygon) > 1:
161 pointlist = [self.point_to_pygame(p) for p in polygon]
162 pygame.draw.lines(self._surface, color, False, pointlist,
163 line_width)
164 if index == mouse_poly and mouse_pos and len(polygon) > 0:
165 endpoint = self.point_to_pymunk(mouse_pos)
166 pygame.draw.line(self._surface, color,
167 self.point_to_pygame(polygon[-1]),
168 self.point_to_pygame(endpoint),
169 line_width // 2)
170 line_found = False # Hack for sane behaviour if lines overlap
171 for line in self.lines:
172 pointlist = [self.point_to_pygame(p) for p in line]
173 if move_point_mode and not self._move_poly and not line_found:
174 if move_point in line:
175 line_found = True
176 pointlist.remove(self.point_to_pygame(move_point))
177 pointlist.append(mouse_pos)
178 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist,
179 line_width)
180 if draw_cand_line and start_pos and mouse_pos:
181 endpoint = level_widget.snap_to_grid(mouse_pos)
182 endpoint = self.point_to_pymunk(endpoint)
183 pointlist = [start_pos,
184 self.point_to_pygame(endpoint)]
185 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
186 return self._surface
187
188 def reset_objs(self):
189 # Reset the object state - needed when changing stuff
190 self.drawables = []
191 self.overlay_drawables = []
192 self._glue = np.PuzzleGlue()
193 for game_object_dict in self._game_objects:
194 obj = self._create_game_object(pymunk.Space(), **game_object_dict)
195 self.lookup[obj] = game_object_dict
196 for enemy_dict in self._enemies:
197 obj = self._create_enemy(pymunk.Space(), **enemy_dict)
198 self.lookup[obj] = enemy_dict
199
200 def get_class(self, classname, mod=None):
201 # Get the class given the classname
202 modules = {
203 'game_object': ngo,
204 'collectable': collectable,
205 'enemies': ne,
206 'puzzle': np,
207 }
208 if '.' in classname:
209 modname, classname = classname.split('.')
210 mod = modules[modname]
211 if mod is None:
212 mod = ngo
213 return getattr(mod, classname)
214
215 def try_new_object(self, classname, target, new, old=None):
216 if old in target:
217 target.remove(old)
218 try:
219 target.append(new)
220 self.reset_objs()
221 test_surface = pygame.surface.Surface((self.x, self.y))
222 # Check for initialisation stuff that happens in render
223 for thing in self.drawables:
224 if not isinstance(thing, ne.Enemy):
225 thing.render(test_surface)
226 return True
227 except Exception as e:
228 target.remove(new)
229 if old is not None:
230 target.append(old)
231 self.reset_objs()
232 alert("Failed to update object %s: %s" % (classname, e))
233 return False
234
235 def find_obj_at_pos(self, mouse_pos):
236 pymunk_pos = self.point_to_pymunk(mouse_pos)
237 # Search visible objects
238 for obj in self.drawables:
239 if obj.get_shape().point_query(pymunk_pos):
240 return obj
241 return None
242
243 def find_vertex(self, mouse_pos):
244 # search for vertexes closest to where we've killed
245 mindist = 1000
246 move_point = None
247 search_point = self.point_to_pymunk(mouse_pos)
248 for index, polygon in self.polygons.items():
249 for point in polygon:
250 dist = distance(point, search_point)
251 if dist < mindist:
252 mindist = dist
253 move_point = point
254 self._move_poly = index
255 # Also check lines
256 for line in self.lines:
257 for point in line:
258 dist = distance(point, search_point)
259 if dist < mindist:
260 mindist = dist
261 move_point = point
262 self._move_poly = None
263 return move_point
264
265 def translate_poly(self, poly, move_point, new_point):
266 dx = new_point[0] - move_point[0]
267 dy = new_point[1] - move_point[1]
268 new_poly = [(p[0] + dx, p[1] + dy) for p in poly]
269 return new_poly
270
271 def replace_poly(self, move_point, new_point):
272 new_point = self.point_to_pymunk(new_point)
273 if self._move_poly:
274 self.polygons[self._move_poly] = self.translate_poly(
275 self.polygons[self._move_poly], move_point, new_point)
276
277 def replace_vertex(self, old_point, new_point):
278 new_point = self.point_to_pymunk(new_point)
279 if self._move_poly:
280 new_polygon = [p if p != old_point else new_point for p in
281 self.polygons[self._move_poly]]
282 self.polygons[self._move_poly] = new_polygon
283 else:
284 for line in self.lines:
285 if old_point in line:
286 line.remove(old_point)
287 line.append(new_point)
288 break
289
290
291class ObjectTable(TableView):
292
293 columns = [TableColumn("Object", 690, 'l', '%r')]
294
295 def __init__(self, data):
296 super(ObjectTable, self).__init__(height=450)
297 self.data = sorted(data,
298 key=lambda d: (d.get('classname'), d.get('name')))
299 self.selected_row = -1
300
301 def num_rows(self):
302 return len(self.data)
303
304 def row_data(self, i):
305 data = self.data[i]
306 if 'name' in data:
307 return ('%s (%s)' % (data['classname'], data['name']), )
308 return (data['classname'], )
309
310 def row_is_selected(self, i):
311 return self.selected_row == i
312
313 def click_row(self, i, ev):
314 self.selected_row = i
315
316 def get_selection(self):
317 if self.selected_row >= 0:
318 return self.data[self.selected_row]
319 return None
320
321
322class EditClassDialog(Dialog):
323
324 def __init__(self, classname, cls, data, level_widget,
325 delete=False):
326 super(EditClassDialog, self).__init__()
327 self.level_widget = level_widget
328 self.classname = classname
329 self.rect = pygame.rect.Rect(0, 0, 900, 550)
330 title = Label("Editing %s" % classname)
331 title.rect = pygame.rect.Rect(100, 10, 600, 25)
332 self.add(title)
333 self.requires = cls.requires()
334 y = 40
335 self.fields = {}
336 index = 0
337 self.poly_field = None
338 self.needs_cleanup = False
339 for requirement, hint in self.requires:
340 label = Label(requirement)
341 label.rect = pygame.rect.Rect(40, y, 200, 25)
342 self.add(label)
343 field = TextField()
344 field.rect = pygame.rect.Rect(220, y, 400, 25)
345 self.add(field)
346 if data is not None:
347 if requirement in data:
348 field.set_text('%s' % data[requirement])
349 elif 'args' in data and requirement != 'name':
350 # NB: The ordering assumptions in requires should make
351 # this safe, but it's really, really, really fragile
352 try:
353 field.set_text('%s' % data['args'][index])
354 index += 1
355 except IndexError:
356 # Assumed to be arguments with the default value
357 pass
358 if hint.startswith('polygon'):
359 self.poly_field = field
360 self.fields[requirement] = field
361 hintlabel = Label(hint)
362 hintlabel.rect = pygame.rect.Rect(640, y, 250, 25)
363 self.add(hintlabel)
364 y += 30
365 if self.poly_field:
366 y += 40
367 label = Label("Polygon to use:")
368 label.rect = pygame.rect.Rect(40, y, 200, 25)
369 self.add(label)
370 self.poly_choice = TextField()
371 self.poly_choice.rect = pygame.Rect(280, y, 400, 25)
372 self.add(self.poly_choice)
373 y += 30
374 button = Button('Use Polygon X', action=self.get_poly)
375 button.rect = pygame.rect.Rect(350, y, 250, 30)
376 self.add(button)
377 buttons = []
378 if delete:
379 labels = ['OK', 'Delete', 'Cancel']
380 else:
381 labels = ['OK', 'Cancel']
382 for text in labels:
383 but = Button(text, action=lambda x=text: self.dismiss(x))
384 buttons.append(but)
385 row = Row(buttons)
386 row.rect = pygame.rect.Rect(250, 500, 700, 50)
387 self.add(row)
388
389 def get_poly(self):
390 try:
391 try:
392 index = int(self.poly_choice.get_text())
393 except ValueError:
394 index = 0
395 data = self.level_widget.level.polygons[index][:]
396 except KeyError:
397 data = []
398 if data:
399 # We unclose the polygon, because that's what pymunk
400 # wants
401 if data[0] == data[-1] and len(data) > 1:
402 data.pop()
403 data = [list(x) for x in data]
404 self.needs_cleanup = True
405 self.poly_field.set_text('%s' % data)
406
407 def cleanup(self):
408 if self.needs_cleanup:
409 self.level_widget.level.polygons[6] = []
410 self.level_widget.invalidate()
411
412 def get_data(self):
413 result = {}
414 result['classname'] = self.classname
415 args = []
416 # We arrange to bounce this through yaml'ish to convert
417 # stuff to the expected type
418 for val, _ in self.requires:
419 text = self.fields[val].get_text()
420 if not text:
421 # skip empty fields
422 continue
423 if val == 'name':
424 result['name'] = text
425 elif self.fields[val] == self.poly_field and text:
426 # Evil, but faster than good
427 try:
428 l = eval(text)
429 except Exception:
430 alert("Invalid polygon %s" % text)
431 self.needs_cleanup = False
432 return None
433 # Check for convexity
434 if not pymunk.util.is_convex(l):
435 alert("Invalid polygon %s - not convex" % text)
436 return None
437 if not pymunk.util.is_clockwise(l):
438 l.reverse()
439 if not pymunk.util.is_clockwise(l):
440 alert("Invalid polygon %s - unable to make clockwise"
441 % text)
442 return None
443 args.append(' - - %s' % l[0])
444 for coord in l[1:]:
445 args.append(' - %s' % coord)
446 else:
447 args.append(' - ' + text)
448 data = "args:\n" + '\n'.join(args)
449 result['args'] = load_s(data)['args']
450 return result
451
452
453class LevelWidget(Widget):
454
455 def __init__(self, level, parent):
456 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
457 SCREEN[0], SCREEN[1]))
458 self.level = level
459 self.pos = (0, 0)
460 self.filled_mode = False
461 self.mouse_pos = None
462 self.cur_poly = None
463 self._mouse_drag = False
464 self._draw_objects = False
465 self._draw_enemies = False
466 self._draw_lines = False
467 self.grid_size = 1
468 self.sel_mode = False
469 self.move_obj_mode = False
470 self.move_obj = None
471 self._start_pos = None
472 self._parent = parent
473 self._move_point_mode = False
474 self._move_poly_mode = False
475 self._move_point = False
476 self._zoom_factor = 1.0
477
478 def _level_coordinates(self, pos):
479 # Move positions to level values
480 if not pos:
481 return (0, 0)
482 # Apply zoom_factor
483 pos = pos[0] + self.pos[0], pos[1] + self.pos[1]
484 zoomed = (pos[0] * self._zoom_factor, pos[1] * self._zoom_factor)
485 return int(zoomed[0]), int(zoomed[1])
486
487 def _move_view(self, offset):
488 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
489 if new_pos[0] < 0:
490 new_pos[0] = self.pos[0]
491 elif new_pos[0] > self.level.x - SCREEN[0]:
492 new_pos[0] = self.pos[0]
493 if new_pos[1] < 0:
494 new_pos[1] = self.pos[1]
495 elif new_pos[1] > self.level.y - SCREEN[1]:
496 new_pos[1] = self.pos[1]
497 self.pos = tuple(new_pos)
498
499 def inc_grid_size(self, amount):
500 self.grid_size = max(1, self.grid_size + amount)
501 if self.grid_size > 1:
502 self.grid_size = self.grid_size - (self.grid_size % 5)
503
504 def snap_to_grid(self, pos):
505 x = pos[0] - (pos[0] % self.grid_size)
506 y = pos[1] - (pos[1] % self.grid_size)
507 return (x, y)
508
509 def set_objects(self, value):
510 if self._draw_objects != value:
511 self._draw_objects = value
512 self.invalidate()
513
514 def set_enemies(self, value):
515 if self._draw_enemies != value:
516 self._draw_enemies = value
517 self.invalidate()
518
519 def zoom_out(self):
520 self._zoom_factor = self._zoom_factor * 2.0
521 if self._zoom_factor > 8:
522 self._zoom_factor = 8
523
524 def zoom_in(self):
525 self._zoom_factor = self._zoom_factor // 2.0
526 if self._zoom_factor < 1:
527 self._zoom_factor = 1
528
529 def draw(self, surface):
530 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
531 and len(self.level.polygons[self.cur_poly])):
532 # We have an active polygon
533 mouse_pos = self._level_coordinates(self.mouse_pos)
534 mouse_pos = self.snap_to_grid(mouse_pos)
535 elif self._draw_lines:
536 # Interior wall mode
537 mouse_pos = self._level_coordinates(self.mouse_pos)
538 mouse_pos = self.snap_to_grid(mouse_pos)
539 elif self._move_point_mode or self._move_poly_mode:
540 mouse_pos = self._level_coordinates(self.mouse_pos)
541 mouse_pos = self.snap_to_grid(mouse_pos)
542 else:
543 mouse_pos = None
544 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
545 self._draw_lines, self._start_pos,
546 self._move_point_mode, self._move_poly_mode,
547 self._move_point, self._zoom_factor, self)
548 if self._draw_objects:
549 for thing in self.level.drawables:
550 if not isinstance(thing, ne.Enemy):
551 thing.render(level_surface)
552 if self._draw_enemies:
553 for thing in self.level.drawables:
554 if isinstance(thing, ne.Enemy):
555 thing.render(level_surface)
556 surface_area = pygame.rect.Rect(self.pos, SCREEN)
557 zoomed_surface = level_surface.copy()
558 if self._zoom_factor != 1:
559 zoomed_surface = pygame.transform.scale(
560 level_surface,
561 (int(level_surface.get_width() / self._zoom_factor),
562 int(level_surface.get_height() / self._zoom_factor)))
563 surface.blit(zoomed_surface, (0, 0), surface_area)
564
565 def change_poly(self, new_poly):
566 self.cur_poly = new_poly
567 self._draw_lines = False
568 self._move_point_mode = False
569 if self.cur_poly is not None:
570 self._parent.reset_lit_buttons()
571 self.filled_mode = False
572
573 def line_mode(self):
574 self.cur_poly = None
575 self._parent.reset_lit_buttons()
576 self._draw_lines = True
577 self.filled_mode = False
578 self._start_pos = None
579 self._move_point_mode = False
580 self._move_poly_mode = False
581
582 def key_down(self, ev):
583 if ev.key == pgl.K_LEFT:
584 self._move_view((-10, 0))
585 elif ev.key == pgl.K_RIGHT:
586 self._move_view((10, 0))
587 elif ev.key == pgl.K_UP:
588 self._move_view((0, -10))
589 elif ev.key == pgl.K_DOWN:
590 self._move_view((0, 10))
591 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
592 self.change_poly(ev.key - pgl.K_0)
593 elif ev.key == pgl.K_0:
594 self.change_poly(None)
595 elif ev.key == pgl.K_d and self.cur_poly:
596 self.level.delete_point(self.cur_poly)
597 elif ev.key == pgl.K_f:
598 self.set_filled()
599 elif ev.key == pgl.K_c:
600 self.close_poly()
601
602 def set_move_poly_mode(self):
603 self._draw_lines = False
604 self._move_point_mode = False
605 self._move_poly_mode = True
606 self.filled_mode = False
607 self._parent.reset_lit_buttons()
608 self._move_point = None
609
610 def set_move_mode(self):
611 self._draw_lines = False
612 self._move_point_mode = True
613 self._move_poly_mode = False
614 self.filled_mode = False
615 self._parent.reset_lit_buttons()
616 self._move_point = None
617
618 def set_filled(self):
619 closed, _ = self.level.all_closed()
620 if closed:
621 self.cur_poly = None
622 self._parent.reset_lit_buttons()
623 self.filled_mode = True
624 self._draw_lines = False
625 self._move_point_mode = False
626 self._move_poly_mode = False
627 else:
628 alert('Not all polygons closed, so not filling')
629
630 def mouse_move(self, ev):
631 old_pos = self.mouse_pos
632 self.mouse_pos = ev.pos
633 if self.move_obj:
634 corrected_pos = self._level_coordinates(ev.pos)
635 snapped_pos = self.snap_to_grid(corrected_pos)
636 self._update_pos(self.move_obj, snapped_pos)
637 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines
638 or self._move_point_mode
639 or self._move_poly_mode):
640 self.invalidate()
641
642 def mouse_drag(self, ev):
643 if self._mouse_drag:
644 old_pos = self.mouse_pos
645 self.mouse_pos = ev.pos
646 diff = (-self.mouse_pos[0] + old_pos[0],
647 -self.mouse_pos[1] + old_pos[1])
648 self._move_view(diff)
649 self.invalidate()
650
651 def mouse_down(self, ev):
652 corrected_pos = self._level_coordinates(ev.pos)
653 snapped_pos = self.snap_to_grid(corrected_pos)
654 if self.sel_mode and ev.button == 1:
655 obj = self.level.find_obj_at_pos(corrected_pos)
656 if obj is not None:
657 self._edit_selected(obj)
658 elif self.move_obj_mode and ev.button == 1 and not self.move_obj:
659 obj = self.level.find_obj_at_pos(corrected_pos)
660 if obj is not None:
661 if obj.movable():
662 self.move_obj = obj
663 data = self.level.lookup[obj]
664 elif self.move_obj_mode and ev.button == 1 and self.move_obj:
665 self._update_pos(self.move_obj, snapped_pos)
666 self.move_obj = None
667 elif self._move_poly_mode and ev.button == 1:
668 if self._move_point:
669 # Place the current point
670 self.level.replace_poly(self._move_point, snapped_pos)
671 self._move_point = None
672 self.invalidate()
673 else:
674 # find the current point
675 self._move_point = self.level.find_vertex(corrected_pos)
676 elif self._move_point_mode and ev.button == 1:
677 if self._move_point:
678 # Place the current point
679 self.level.replace_vertex(self._move_point, snapped_pos)
680 self._move_point = None
681 self.invalidate()
682 else:
683 # find the current point
684 self._move_point = self.level.find_vertex(corrected_pos)
685 elif ev.button == 1:
686 if self._draw_lines:
687 if self._start_pos is None:
688 self._start_pos = snapped_pos
689 else:
690 self.level.add_line(self._start_pos, snapped_pos)
691 self._start_pos = None
692 else:
693 print "Click: %r" % (
694 self.level.point_to_pymunk(corrected_pos),)
695 if ev.button == 4: # Scroll up
696 self._move_view((0, -10))
697 elif ev.button == 5: # Scroll down
698 self._move_view((0, 10))
699 elif ev.button == 6: # Scroll left
700 self._move_view((-10, 0))
701 elif ev.button == 7: # Scroll right
702 self._move_view((10, 0))
703 elif self.cur_poly and ev.button == 1:
704 # Add a point
705 if not self.level.add_point(self.cur_poly, snapped_pos):
706 alert("Failed to place point")
707 elif ev.button == 3:
708 self._mouse_drag = True
709
710 def mouse_up(self, ev):
711 if ev.button == 3:
712 self._mouse_drag = False
713
714 def close_poly(self):
715 if self.cur_poly is None:
716 return
717 if self.level.close_poly(self.cur_poly):
718 alert("Successfully closed the polygon")
719 self.change_poly(None)
720 else:
721 alert("Failed to close the polygon")
722
723 def _edit_class(self, classname, cls, data):
724 # Dialog for class properties
725 dialog = EditClassDialog(classname, cls, data, self)
726 if dialog.present() == 'OK':
727 return dialog
728 return None
729
730 def _edit_selected(self, obj):
731 data = self.level.lookup[obj]
732 cls = obj.__class__
733 classname = obj.__class__.__name__
734 dialog = EditClassDialog(classname, cls, data, self, True)
735 res = dialog.present()
736 if res == 'OK':
737 edited = dialog.get_data()
738 if edited is not None:
739 for target in [self.level._game_objects, self.level._enemies]:
740 if data in target:
741 if self.level.try_new_object(classname, target,
742 edited, data):
743 dialog.cleanup()
744 break
745 elif res == 'Delete':
746 for target in [self.level._game_objects, self.level._enemies]:
747 if data in target:
748 target.remove(data)
749 self.level.reset_objs()
750 break
751
752 def _make_edit_dialog(self, entries):
753 # Dialog to hold the editor
754 edit_box = Dialog()
755 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
756 table = ObjectTable(entries)
757 edit_box.add(table)
758 buttons = []
759 for text in ['OK', 'Delete', 'Cancel']:
760 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
761 buttons.append(but)
762 row = Row(buttons)
763 row.rect = pygame.rect.Rect(250, 450, 700, 50)
764 edit_box.add(row)
765 edit_box.get_selection = lambda: table.get_selection()
766 return edit_box
767
768 def edit_objects(self):
769 edit_box = self._make_edit_dialog(self.level._game_objects)
770 res = edit_box.present()
771 choice = edit_box.get_selection()
772 if choice is None:
773 return
774 if res == 'OK':
775 cls = self.level.get_class(choice['classname'])
776 edit_dlg = self._edit_class(choice['classname'], cls, choice)
777 if edit_dlg is not None:
778 edited = edit_dlg.get_data()
779 if self.level.try_new_object(choice["classname"],
780 self.level._game_objects,
781 edited, choice):
782 edit_dlg.cleanup()
783 elif res == 'Delete':
784 self.level._game_objects.remove(choice)
785 self.level.reset_objs()
786
787 def edit_enemies(self):
788 edit_box = self._make_edit_dialog(self.level._enemies)
789 res = edit_box.present()
790 choice = edit_box.get_selection()
791 if choice is None:
792 return
793 if res == 'OK':
794 cls = self.level.get_class(choice['classname'], ne)
795 edit_dlg = self._edit_class(choice['classname'], cls, choice)
796 if edit_dlg is not None:
797 edited = edit_dlg.get_data()
798 if self.level.try_new_object(choice["classname"],
799 self.level._enemies,
800 edited, choice):
801 edit_dlg.cleanup()
802 elif res == 'Delete':
803 self.level._enemies.remove(choice)
804 self.level.reset_objs()
805
806 def _make_choice_dialog(self, classes):
807 # Dialog to hold the editor
808 data = []
809 for cls_name, cls in classes:
810 data.append({"classname": cls_name, "class": cls})
811 choice_box = Dialog()
812 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
813 table = ObjectTable(data)
814 choice_box.add(table)
815 buttons = []
816 for text in ['OK', 'Cancel']:
817 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
818 buttons.append(but)
819 row = Row(buttons)
820 row.rect = pygame.rect.Rect(250, 450, 700, 50)
821 choice_box.add(row)
822 choice_box.get_selection = lambda: table.get_selection()
823 return choice_box
824
825 def add_game_object(self):
826 classes = ngo.get_editable_game_objects()
827 classes.extend(("collectable.%s" % cls_name, cls)
828 for cls_name, cls
829 in collectable.get_editable_game_objects())
830 choose = self._make_choice_dialog(classes)
831 res = choose.present()
832 choice = choose.get_selection()
833 if res == 'OK' and choice is not None:
834 classname = choice['classname']
835 cls = choice['class']
836 edit_dlg = self._edit_class(classname, cls, None)
837 if edit_dlg is not None:
838 new_cls = edit_dlg.get_data()
839 if self.level.try_new_object(classname,
840 self.level._game_objects,
841 new_cls, None):
842 edit_dlg.cleanup()
843
844 def add_enemy(self):
845 classes = ne.get_editable_enemies()
846 choose = self._make_choice_dialog(classes)
847 res = choose.present()
848 choice = choose.get_selection()
849 if res == 'OK' and choice is not None:
850 classname = choice['classname']
851 cls = choice['class']
852 edit_dlg = self._edit_class(classname, cls, None)
853 if edit_dlg is not None:
854 new_cls = edit_dlg.get_data()
855 if self.level.try_new_object(classname, self.level._enemies,
856 new_cls, None):
857 edit_dlg.cleanup()
858
859 def add_puzzler(self):
860 classes = np.get_editable_puzzlers()
861 choose = self._make_choice_dialog(classes)
862 res = choose.present()
863 choice = choose.get_selection()
864 if res == 'OK' and choice is not None:
865 classname = choice['classname']
866 cls = choice['class']
867 edit_dlg = self._edit_class(classname, cls, None)
868 if edit_dlg is not None:
869 new_cls = edit_dlg.get_data()
870 if self.level.try_new_object(classname,
871 self.level._game_objects,
872 new_cls, None):
873 edit_dlg.cleanup()
874
875 def _update_pos(self, obj, new_pos):
876 data = self.level.lookup[obj]
877 new_coords = self.level.point_to_pymunk(new_pos)
878 args = data['args']
879 old_coords = list(args[0])
880 args[0][0] = new_coords[0]
881 args[0][1] = new_coords[1]
882 param_defs = obj.requires()[1:] # chop off name
883 for i, (_key, key_type) in enumerate(param_defs):
884 if i > len(args):
885 break
886 if key_type == "polygon (convex)":
887 args[i] = self.level.translate_poly(
888 args[i], old_coords, new_coords)
889 self.level.reset_objs()
890 self.invalidate()
891
892
893class HighLightButton(Button):
894 """Button with highlight support"""
895 def __init__(self, text, parent, **kwds):
896 super(HighLightButton, self).__init__(text, **kwds)
897 self._parent = parent
898
899 def highlight(self):
900 self.border_color = pygame.color.Color('red')
901
902 def reset(self):
903 self.border_color = self.fg_color
904
905
906class PolyButton(HighLightButton):
907 """Button for coosing the correct polygon"""
908
909 def __init__(self, index, level_widget, parent):
910 if index is not None:
911 text = "P %s" % index
912 else:
913 text = 'Exit Draw Mode'
914 super(PolyButton, self).__init__(text, parent)
915 self.index = index
916 self.level_widget = level_widget
917
918 def action(self):
919 self.level_widget.change_poly(self.index)
920 self._parent.reset_lit_buttons()
921 if self.index is not None:
922 self.highlight()
923
924
925class GridSizeLabel(Label):
926 """Label and setter for grid size."""
927
928 def __init__(self, level_widget, **kwds):
929 self.level_widget = level_widget
930 super(GridSizeLabel, self).__init__(self.grid_text(), **kwds)
931
932 def grid_text(self):
933 return "Grid size: %d" % self.level_widget.grid_size
934
935 def inc_grid_size(self, amount):
936 self.level_widget.inc_grid_size(amount)
937 self.set_text(self.grid_text())
938
939
940class SnapButton(Button):
941 """Button for increasing or decreasing snap-to-grid size."""
942
943 def __init__(self, grid_size_label, parent, inc_amount):
944 self.grid_size_label = grid_size_label
945 self.inc_amount = inc_amount
946 text = "Grid %s%d" % (
947 '-' if inc_amount < 0 else '+',
948 abs(inc_amount))
949 self._parent = parent
950 super(SnapButton, self).__init__(text)
951
952 def action(self):
953 self.grid_size_label.inc_grid_size(self.inc_amount)
954
955
956class EditorApp(RootWidget):
957
958 def __init__(self, level, surface):
959 super(EditorApp, self).__init__(surface)
960 self.level = level
961 self.level_widget = LevelWidget(self.level, self)
962 self.add(self.level_widget)
963
964 self._dMenus = {}
965
966 self._light_buttons = []
967
968 self._make_draw_menu()
969 self._make_objects_menu()
970
971 self._menu_mode = 'drawing'
972 self._populate_menu()
973
974 self._zoom = 1
975
976 def _make_draw_menu(self):
977 widgets = []
978
979 white = pygame.color.Color("white")
980
981 # Add poly buttons
982 y = 5
983 for poly in range(1, 10):
984 but = PolyButton(poly, self.level_widget, self)
985 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 3 - MENU_PAD,
986 MENU_BUTTON_HEIGHT)
987 index = poly % 3
988 if index == 1:
989 but.rect.move_ip(MENU_LEFT, y)
990 elif index == 2:
991 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 3 - MENU_HALF_PAD,
992 y)
993 else:
994 left = MENU_LEFT + 2 * MENU_WIDTH // 3 - MENU_HALF_PAD
995 but.rect.move_ip(left, y)
996 y += MENU_BUTTON_HEIGHT + MENU_PAD
997 self._light_buttons.append(but)
998 widgets.append(but)
999
1000 end_poly_but = PolyButton(None, self.level_widget, self)
1001 end_poly_but.rect = BUTTON_RECT.copy()
1002 end_poly_but.rect.move_ip(MENU_LEFT, y)
1003 widgets.append(end_poly_but)
1004 y += MENU_BUTTON_HEIGHT + MENU_PAD
1005
1006 self.move_point_but = HighLightButton("Mv Point", self,
1007 action=self.move_point)
1008 self.move_point_but.rect = HALF_BUTTON_RECT.copy()
1009 self.move_point_but.rect.move_ip(MENU_LEFT, y)
1010 widgets.append(self.move_point_but)
1011 self._light_buttons.append(self.move_point_but)
1012
1013 self.move_poly_but = HighLightButton("Mv Poly", self,
1014 action=self.move_poly)
1015 self.move_poly_but.rect = HALF_BUTTON_RECT.copy()
1016 self.move_poly_but.rect.move_ip(MENU_LEFT + MENU_HALF_WIDTH, y)
1017 widgets.append(self.move_poly_but)
1018 self._light_buttons.append(self.move_poly_but)
1019
1020 y += MENU_BUTTON_HEIGHT + MENU_PAD
1021
1022 # grid size widgets
1023 grid_size_label = GridSizeLabel(
1024 self.level_widget, width=BUTTON_RECT.width,
1025 align="c", fg_color=white)
1026 grid_size_label.rect.move_ip(MENU_LEFT, y)
1027 widgets.append(grid_size_label)
1028 y += grid_size_label.rect.height + MENU_PAD
1029 inc_snap_but = SnapButton(grid_size_label, self, 5)
1030 inc_snap_but.rect = HALF_BUTTON_RECT.copy()
1031 inc_snap_but.rect.move_ip(MENU_LEFT, y)
1032 widgets.append(inc_snap_but)
1033 dec_snap_but = SnapButton(grid_size_label, self, -5)
1034 dec_snap_but.rect = HALF_BUTTON_RECT.copy()
1035 dec_snap_but.rect.move_ip(
1036 MENU_LEFT + MENU_HALF_WIDTH, y)
1037 widgets.append(dec_snap_but)
1038 y += MENU_BUTTON_HEIGHT + MENU_PAD
1039
1040 self.draw_line_but = HighLightButton("Draw interior wall", self,
1041 action=self.set_line_mode)
1042 self.draw_line_but.rect = BUTTON_RECT.copy()
1043 self.draw_line_but.rect.move_ip(MENU_LEFT, y)
1044 widgets.append(self.draw_line_but)
1045 self._light_buttons.append(self.draw_line_but)
1046 y += MENU_BUTTON_HEIGHT + MENU_PAD
1047
1048 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
1049 fill_but.rect = BUTTON_RECT.copy()
1050 fill_but.rect.move_ip(MENU_LEFT, y)
1051 widgets.append(fill_but)
1052 y += MENU_BUTTON_HEIGHT + MENU_PAD
1053
1054 close_poly_but = Button('Close Polygon',
1055 action=self.level_widget.close_poly)
1056 close_poly_but.rect = BUTTON_RECT.copy()
1057 close_poly_but.rect.move_ip(MENU_LEFT, y)
1058 widgets.append(close_poly_but)
1059 y += MENU_BUTTON_HEIGHT + MENU_PAD
1060
1061 self.show_objs = CheckBox(fg_color=white)
1062 self.show_objs.rect = CHECK_RECT.copy()
1063 self.show_objs.rect.move_ip(MENU_LEFT, y)
1064 label = Label("Show Objects", fg_color=white)
1065 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1066 widgets.append(self.show_objs)
1067 widgets.append(label)
1068 y += label.rect.height + MENU_PAD
1069
1070 self.show_enemies = CheckBox(fg_color=white)
1071 self.show_enemies.rect = CHECK_RECT.copy()
1072 self.show_enemies.rect.move_ip(MENU_LEFT, y)
1073 label = Label("Show enemy start pos", fg_color=white)
1074 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
1075 widgets.append(self.show_enemies)
1076 widgets.append(label)
1077 y += label.rect.height + MENU_PAD
1078
1079 y += MENU_PAD
1080 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
1081 switch_but.rect = BUTTON_RECT.copy()
1082 switch_but.rect.move_ip(MENU_LEFT, y)
1083 widgets.append(switch_but)
1084 y += switch_but.rect.height + MENU_PAD
1085
1086 save_but = Button('Save Level', action=self.save)
1087 save_but.rect = BUTTON_RECT.copy()
1088 save_but.rect.move_ip(MENU_LEFT, y)
1089 widgets.append(save_but)
1090 y += MENU_BUTTON_HEIGHT + MENU_PAD
1091
1092 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1093 zoom_out.rect = BUTTON_RECT.copy()
1094 zoom_out.rect.width = zoom_out.rect.width // 2
1095 zoom_out.rect.move_ip(MENU_LEFT, y)
1096 widgets.append(zoom_out)
1097
1098 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1099 zoom_in.rect = BUTTON_RECT.copy()
1100 zoom_in.width = zoom_in.width // 2
1101 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1102 widgets.append(zoom_in)
1103
1104 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1105 quit_but = Button('Quit', action=self.do_quit)
1106 quit_but.rect = BUTTON_RECT.copy()
1107 quit_but.rect.move_ip(MENU_LEFT, y)
1108 widgets.append(quit_but)
1109
1110 self._dMenus['drawing'] = widgets
1111
1112 def _make_objects_menu(self):
1113 widgets = []
1114
1115 # Add poly buttons
1116 y = 15
1117
1118 edit_objs_but = Button('Edit Objects',
1119 action=self.level_widget.edit_objects)
1120 edit_objs_but.rect = BUTTON_RECT.copy()
1121 edit_objs_but.rect.move_ip(MENU_LEFT, y)
1122 widgets.append(edit_objs_but)
1123 y += MENU_BUTTON_HEIGHT + MENU_PAD
1124
1125 edir_enemies_but = Button('Edit Enemies',
1126 action=self.level_widget.edit_enemies)
1127 edir_enemies_but.rect = BUTTON_RECT.copy()
1128 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
1129 widgets.append(edir_enemies_but)
1130 y += MENU_BUTTON_HEIGHT + MENU_PAD
1131
1132 add_obj_but = Button('Add Game Object',
1133 action=self.level_widget.add_game_object)
1134 add_obj_but.rect = BUTTON_RECT.copy()
1135 add_obj_but.rect.move_ip(MENU_LEFT, y)
1136 widgets.append(add_obj_but)
1137 y += MENU_BUTTON_HEIGHT + MENU_PAD
1138
1139 add_puzzle_but = Button('Add Puzzler',
1140 action=self.level_widget.add_puzzler)
1141 add_puzzle_but.rect = BUTTON_RECT.copy()
1142 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
1143 widgets.append(add_puzzle_but)
1144 y += MENU_BUTTON_HEIGHT + MENU_PAD
1145
1146 add_enemy_but = Button('Add Enemy',
1147 action=self.level_widget.add_enemy)
1148 add_enemy_but.rect = BUTTON_RECT.copy()
1149 add_enemy_but.rect.move_ip(MENU_LEFT, y)
1150 widgets.append(add_enemy_but)
1151 y += MENU_BUTTON_HEIGHT + MENU_PAD
1152
1153 y += MENU_PAD
1154 self.sel_mode_but = HighLightButton('Select Object', self,
1155 action=self.sel_mode)
1156 self.sel_mode_but.rect = BUTTON_RECT.copy()
1157 self.sel_mode_but.rect.move_ip(MENU_LEFT, y)
1158 widgets.append(self.sel_mode_but)
1159 self._light_buttons.append(self.sel_mode_but)
1160 y += MENU_BUTTON_HEIGHT + MENU_PAD
1161
1162 y += MENU_PAD
1163 self.move_obj_mode_but = HighLightButton('Move Object', self,
1164 action=self.move_obj_mode)
1165 self.move_obj_mode_but.rect = BUTTON_RECT.copy()
1166 self.move_obj_mode_but.rect.move_ip(MENU_LEFT, y)
1167 widgets.append(self.move_obj_mode_but)
1168 self._light_buttons.append(self.move_obj_mode_but)
1169 y += MENU_BUTTON_HEIGHT + MENU_PAD
1170
1171 y += MENU_PAD
1172 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
1173 switch_but.rect = BUTTON_RECT.copy()
1174 switch_but.rect.move_ip(MENU_LEFT, y)
1175 widgets.append(switch_but)
1176 y += switch_but.rect.height + MENU_PAD
1177
1178 save_but = Button('Save Level', action=self.save)
1179 save_but.rect = BUTTON_RECT.copy()
1180 save_but.rect.move_ip(MENU_LEFT, y)
1181 widgets.append(save_but)
1182 y += MENU_BUTTON_HEIGHT + MENU_PAD
1183
1184 zoom_out = Button('Zoom out', action=self.level_widget.zoom_out)
1185 zoom_out.rect = BUTTON_RECT.copy()
1186 zoom_out.rect.width = zoom_out.rect.width // 2
1187 zoom_out.rect.move_ip(MENU_LEFT, y)
1188 widgets.append(zoom_out)
1189
1190 zoom_in = Button('Zoom in', action=self.level_widget.zoom_in)
1191 zoom_in.rect = BUTTON_RECT.copy()
1192 zoom_in.width = zoom_in.width // 2
1193 zoom_in.rect.move_ip(MENU_LEFT + zoom_out.width, y)
1194 widgets.append(zoom_in)
1195 y += MENU_BUTTON_HEIGHT + MENU_PAD
1196
1197 y = SCREEN[1] - MENU_BUTTON_HEIGHT - MENU_PAD
1198 quit_but = Button('Quit', action=self.do_quit)
1199 quit_but.rect = BUTTON_RECT.copy()
1200 quit_but.rect.move_ip(MENU_LEFT, y)
1201 widgets.append(quit_but)
1202
1203 self._dMenus['objects'] = widgets
1204
1205 def key_down(self, ev):
1206 if ev.key == pgl.K_ESCAPE:
1207 self.do_quit()
1208 elif ev.key == pgl.K_s:
1209 self.save()
1210 else:
1211 self.level_widget.key_down(ev)
1212
1213 def do_quit(self):
1214 res = ask("Really Quit?")
1215 if res == "OK":
1216 self.quit()
1217
1218 def save(self):
1219 closed, messages = self.level.all_closed()
1220 if closed:
1221 self.level.save()
1222 # display success
1223 alert("Level %s saved successfully." % self.level.name)
1224 else:
1225 # display errors
1226 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
1227
1228 def switch_to_draw(self):
1229 if self._menu_mode != 'drawing':
1230 self._clear_menu()
1231 self._menu_mode = 'drawing'
1232 self._populate_menu()
1233
1234 def switch_to_objects(self):
1235 if self._menu_mode != 'objects':
1236 self._clear_menu()
1237 self._menu_mode = 'objects'
1238 self._populate_menu()
1239
1240 def _clear_menu(self):
1241 for widget in self._dMenus[self._menu_mode]:
1242 self.remove(widget)
1243
1244 def reset_lit_buttons(self):
1245 for but in self._light_buttons:
1246 but.reset()
1247
1248 def _populate_menu(self):
1249 self.level_widget.change_poly(None)
1250 self.level_widget.sel_mode = False
1251 self.level_widget.move_obj_mode = False
1252 self.level_widget.move_obj = None
1253 for widget in self._dMenus[self._menu_mode]:
1254 self.add(widget)
1255 self.invalidate()
1256
1257 def set_line_mode(self):
1258 self.level_widget.line_mode()
1259 self.draw_line_but.highlight()
1260
1261 def sel_mode(self):
1262 self.level_widget.sel_mode = not self.level_widget.sel_mode
1263 if self.level_widget.sel_mode:
1264 self.move_obj_mode_but.reset()
1265 self.sel_mode_but.highlight()
1266 self.level_widget.move_obj_mode = False
1267 self.level_widget.move_obj = None
1268 else:
1269 self.sel_mode_but.reset()
1270
1271 def move_obj_mode(self):
1272 self.level_widget.move_obj_mode = not self.level_widget.move_obj_mode
1273 if self.level_widget.move_obj_mode:
1274 self.sel_mode_but.reset()
1275 self.move_obj_mode_but.highlight()
1276 self.level_widget.sel_mode = False
1277 else:
1278 self.move_obj_mode_but.reset()
1279
1280 def mouse_move(self, ev):
1281 self.level_widget.mouse_move(ev)
1282
1283 def move_point(self):
1284 self.level_widget.set_move_mode()
1285 self.move_point_but.highlight()
1286
1287 def move_poly(self):
1288 self.level_widget.set_move_poly_mode()
1289 self.move_poly_but.highlight()
1290
1291 def draw(self, surface):
1292 # Update checkbox state
1293 if self._menu_mode == 'drawing':
1294 self.level_widget.set_objects(self.show_objs.value)
1295 self.level_widget.set_enemies(self.show_enemies.value)
1296 else:
1297 self.level_widget.set_objects(True)
1298 self.level_widget.set_enemies(True)
1299 super(EditorApp, self).draw(surface)
1300
1301
1302if __name__ == "__main__":
1303 if len(sys.argv) not in [2, 4]:
1304 print 'Please supply a levelname or levelname and level size'
1305 sys.exit()
1306 # Need to ensure we have defaults for rendering
1307 parse_args([])
1308 pygame.display.init()
1309 pygame.font.init()
1310 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
1311 pgl.SWSURFACE)
1312 if len(sys.argv) == 2:
1313 level = EditorLevel(sys.argv[1])
1314 level.load(pymunk.Space())
1315 elif len(sys.argv) == 4:
1316 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
1317 pygame.display.set_caption('Nagslang Area Editor')
1318 pygame.key.set_repeat(200, 100)
1319 app = EditorApp(level, pygame.display.get_surface())
1320 app.run()
Note: See TracBrowser for help on using the repository browser.