source: tools/area_editor.py@ 275:2abb61878bb1

Last change on this file since 275:2abb61878bb1 was 275:2abb61878bb1, checked in by Neil Muller <drnlmuller@…>, 9 years ago

Add a 'select object' with pop-up for easier editing

  • Property exe set to *
File size: 31.7 KB
RevLine 
[71]1#!/usr/bin/env python
2
[51]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> <ysiz>
11# (size specified in pixels
12#
13
[71]14import os
15import sys
16
[51]17import pygame
18import pygame.locals as pgl
19
[152]20sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
21
[153]22import pymunk
23
[108]24from albow.root import RootWidget
25from albow.widget import Widget
[198]26from albow.controls import Button, Label, CheckBox
[225]27from albow.dialogs import alert, Dialog
28from albow.layout import Row
[251]29from albow.fields import TextField
[225]30from albow.table_view import TableView, TableColumn
[108]31
[199]32from nagslang.options import parse_args
[109]33from nagslang.constants import SCREEN
[204]34from nagslang.level import Level, POLY_COLORS, LINE_COLOR
[255]35from nagslang.yamlish import load_s
[251]36import nagslang.enemies as ne
37import nagslang.game_object as ngo
38import nagslang.puzzle as np
[51]39
[108]40# layout constants
41MENU_BUTTON_HEIGHT = 35
[115]42MENU_PAD = 6
43MENU_HALF_PAD = MENU_PAD // 2
44MENU_LEFT = SCREEN[0] + MENU_HALF_PAD
45MENU_WIDTH = 200 - MENU_PAD
[108]46
[220]47BUTTON_RECT = pygame.rect.Rect(0, 0, MENU_WIDTH, MENU_BUTTON_HEIGHT)
48CHECK_RECT = pygame.rect.Rect(0, 0, MENU_BUTTON_HEIGHT // 2,
49 MENU_BUTTON_HEIGHT // 2)
50
[108]51
[51]52class EditorLevel(Level):
53
54 def __init__(self, name, x=800, y=600):
55 super(EditorLevel, self).__init__(name)
56 self.x = x
57 self.y = y
[275]58 # Lookup initiliasition info from the objects
59 self.lookup = {}
[51]60
61 def round_point(self, pos):
62 return (10 * (pos[0] // 10), 10 * (pos[1] // 10))
63
[275]64 def load(self, space):
65 super(EditorLevel, self).load(space)
66 # Needed to fill in the lookup dict
67 self.reset_objs()
68
[51]69 def point_to_pymunk(self, pos):
70 # inverse of point_to_pygame
71 # (this is also the same as point_to_pygame, but a additional
72 # function for sanity later in pyweek).
73 return (pos[0], self.y - pos[1])
74
75 def add_point(self, poly_index, pos):
76 self.polygons.setdefault(poly_index, [])
77 if not self.polygons[poly_index]:
[99]78 point = self.point_to_pymunk(self.round_point(pos))
79 self.polygons[poly_index].append(point)
[51]80 else:
[205]81 add_pos = self.fix_poly_angle(poly_index, pos)
[51]82 self.polygons[poly_index].append(add_pos)
83
[205]84 def _fix_angle(self, point1, pos):
[51]85 # We want the line (point1 to pos) to be an angle of
86 # 0, 45, 90, 135, 180, 225, 270, 305
87 # However, we only need to consider half the circle
88 # This is a hack to approximate the right thing
89 pos0 = (pos[0], point1[1])
90 pos90 = (point1[0], pos[1])
91 dist = max(abs(point1[0] - pos[0]), abs(point1[1] - pos[1]))
92 pos45 = (point1[0] + dist, point1[1] + dist)
93 pos135 = (point1[0] + dist, point1[1] - dist)
94 pos225 = (point1[0] - dist, point1[1] - dist)
95 pos305 = (point1[0] - dist, point1[1] + dist)
96 min_dist = 9999999
97 new_pos = point1
98 for cand in [pos0, pos90, pos45, pos135, pos225, pos305]:
99 dist = (pos[0] - cand[0]) ** 2 + (pos[1] - cand[1]) ** 2
100 if dist < min_dist:
101 new_pos = cand
102 min_dist = dist
103 return self.point_to_pymunk(new_pos)
104
[205]105 def fix_line_angle(self, start_pos, pos):
106 start_pos = self.round_point(start_pos)
107 pos = self.round_point(pos)
108 return self._fix_angle(start_pos, pos)
109
110 def fix_poly_angle(self, index, pos):
111 # Last point
112 point1 = self.point_to_pygame(self.polygons[index][-1])
113 pos = self.round_point(pos)
114 return self._fix_angle(point1, pos)
115
[51]116 def delete_point(self, index):
117 if index in self.polygons and len(self.polygons[index]) > 0:
118 self.polygons[index].pop()
119
[135]120 def close_poly(self, index):
121 """Attempts to close the current polygon.
122
123 We allow a small additional step to close the polygon, but
124 it's limited as it's a magic point addition"""
125 if len(self.polygons[index]) < 2:
126 # Too small
127 return False
128 first = self.polygons[index][0]
[205]129 if self.fix_poly_angle(index, self.point_to_pygame(first)) == first:
[135]130 self.add_point(index, self.point_to_pygame(first))
131 return True
132 candidates = [(first[0] + 10 * i, first[1]) for
133 i in (-3, -2, -1, 1, 2, 3)]
134 candidates.extend([(first[0], first[1] + 10 * i) for
135 i in (-3, -2, -1, 1, 2, 3)])
136 candidates.extend([(first[0] + 10 * i, first[1] + 10 * i) for
137 i in (-3, -2, -1, 1, 2, 3)])
138 candidates.extend([(first[0] + 10 * i, first[1] - 10 * i) for
139 i in (-3, -2, -1, 1, 2, 3)])
140 min_dist = 99999
141 poss = None
142 for cand in candidates:
[205]143 if self.fix_poly_angle(index, self.point_to_pygame(cand)) == cand:
[135]144 dist = (first[0] - cand[0]) ** 2 + (first[1] - cand[1]) ** 2
145 if dist < min_dist:
146 poss = cand
147 if poss is not None:
148 self.add_point(index, self.point_to_pygame(poss))
149 self.add_point(index, self.point_to_pygame(first))
150 return True
151 return False
152
[205]153 def add_line(self, start_pos, end_pos):
154 endpoint = self.fix_line_angle(start_pos, end_pos)
155 startpoint = self.point_to_pymunk(self.round_point(start_pos))
156 self.lines.append([startpoint, endpoint])
157
158 def draw(self, mouse_pos, mouse_poly, filled, draw_cand_line, start_pos):
[51]159 self._draw_background(True)
160 # Draw polygons as needed for the editor
[96]161 if filled:
162 self._draw_exterior(True)
[51]163 for index, polygon in self.polygons.items():
164 color = POLY_COLORS[index]
165 if len(polygon) > 1:
166 pointlist = [self.point_to_pygame(p) for p in polygon]
167 pygame.draw.lines(self._surface, color, False, pointlist, 2)
168 if index == mouse_poly and mouse_pos:
[205]169 endpoint = self.fix_poly_angle(index, mouse_pos)
[51]170 pygame.draw.line(self._surface, color,
[99]171 self.point_to_pygame(polygon[-1]),
172 self.point_to_pygame(endpoint))
[204]173 for line in self.lines:
174 pointlist = [self.point_to_pygame(p) for p in line]
175 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 2)
[205]176 if draw_cand_line and start_pos and mouse_pos:
177 endpoint = self.fix_line_angle(start_pos, mouse_pos)
178 pointlist = [self.round_point(start_pos),
[206]179 self.point_to_pygame(endpoint)]
[205]180 pygame.draw.lines(self._surface, LINE_COLOR, False, pointlist, 1)
[199]181 return self._surface.copy()
[51]182
[239]183 def reset_objs(self):
184 # Reset the object state - needed when changing stuff
185 self.drawables = []
186 self.overlay_drawables = []
[251]187 self._glue = np.PuzzleGlue()
[239]188 for game_object_dict in self._game_objects:
[275]189 obj = self._create_game_object(pymunk.Space(), **game_object_dict)
190 self.lookup[obj] = game_object_dict
[239]191 for enemy_dict in self._enemies:
[275]192 obj = self._create_enemy(pymunk.Space(), **enemy_dict)
193 self.lookup[obj] = enemy_dict
[239]194
[251]195 def get_class(self, classname, mod=None):
196 # Get the class given the classname
197 modules = {
198 'game_object': ngo,
199 'enemies': ne,
200 'puzzle': np,
201 }
202 if '.' in classname:
203 modname, classname = classname.split('.')
204 mod = modules[modname]
205 if mod is None:
206 mod = ngo
207 return getattr(mod, classname)
208
[262]209 def try_new_object(self, classname, target, new, old=None):
[251]210 if old in target:
211 target.remove(old)
212 try:
213 target.append(new)
214 self.reset_objs()
215 return True
[262]216 except Exception as e:
[251]217 target.remove(new)
218 if old is not None:
219 target.append(old)
220 self.reset_objs()
[262]221 alert("Failed to update object %s: %s" % (classname, e))
[251]222 return False
223
[275]224 def find_obj_at_pos(self, mouse_pos):
225 pymunk_pos = self.point_to_pymunk(mouse_pos)
226 # Search visible objects
227 for obj in self.drawables:
228 if obj.get_shape().point_query(pymunk_pos):
229 return obj
230 return None
231
[109]232
[225]233class ObjectTable(TableView):
234
235 columns = [TableColumn("Object", 690, 'l', '%r')]
236
237 def __init__(self, data):
238 super(ObjectTable, self).__init__(height=450)
239 self.data = data
240 self.selected_row = -1
241
242 def num_rows(self):
243 return len(self.data)
244
245 def row_data(self, i):
246 data = self.data[i]
247 if 'name' in data:
248 return ('%s (%s)' % (data['classname'], data['name']), )
249 return (data['classname'], )
250
251 def row_is_selected(self, i):
252 return self.selected_row == i
253
254 def click_row(self, i, ev):
255 self.selected_row = i
256
257 def get_selection(self):
258 if self.selected_row >= 0:
259 return self.data[self.selected_row]
260 return None
261
262
[251]263class EditClassDialog(Dialog):
264
[275]265 def __init__(self, classname, cls, data, delete=False):
[251]266 super(EditClassDialog, self).__init__()
[255]267 self.classname = classname
[251]268 self.rect = pygame.rect.Rect(0, 0, 800, 550)
269 title = Label("Editing %s" % classname)
270 title.rect = pygame.rect.Rect(100, 10, 600, 25)
271 self.add(title)
[262]272 self.requires = cls.requires()
[251]273 y = 40
274 self.fields = {}
275 index = 0
[262]276 for requirement, hint in self.requires:
[251]277 label = Label(requirement)
278 label.rect = pygame.rect.Rect(40, y, 200, 25)
279 self.add(label)
280 field = TextField()
281 field.rect = pygame.rect.Rect(220, y, 400, 25)
282 self.add(field)
283 if data is not None:
284 if requirement in data:
285 field.set_text('%s' % data[requirement])
[255]286 elif 'args' in data and requirement != 'name':
[251]287 # NB: The ordering assumptions in requires should make
288 # this safe, but it's really, really, really fragile
[255]289 try:
290 field.set_text('%s' % data['args'][index])
291 index += 1
292 except IndexError:
293 # Assumed to be arguments with the default value
294 pass
[251]295 self.fields[requirement] = field
296 hintlabel = Label(hint)
297 hintlabel.rect = pygame.rect.Rect(640, y, 100, 25)
298 self.add(hintlabel)
299 y += 30
300 buttons = []
[275]301 if delete:
302 labels = ['OK', 'Delete', 'Cancel']
303 else:
304 labels = ['OK', 'Cancel']
305 for text in labels:
[251]306 but = Button(text, action=lambda x=text: self.dismiss(x))
307 buttons.append(but)
308 row = Row(buttons)
309 row.rect = pygame.rect.Rect(250, 500, 700, 50)
310 self.add(row)
311
312 def get_data(self):
[255]313 result = {}
314 result['classname'] = self.classname
315 args = []
316 # We arrange to bounce this through yaml'ish to convert
317 # stuff to the expected type
[262]318 for val, _ in self.requires:
[255]319 text = self.fields[val].get_text()
320 if not text:
321 # skip empty fields
322 continue
323 if val == 'name':
324 result['name'] = text
325 else:
326 args.append(' - ' + text)
327 data = "args:\n" + '\n'.join(args)
328 result['args'] = load_s(data)['args']
329 return result
[251]330
331
[108]332class LevelWidget(Widget):
[51]333
[108]334 def __init__(self, level):
335 super(LevelWidget, self).__init__(pygame.rect.Rect(0, 0,
[109]336 SCREEN[0], SCREEN[1]))
[51]337 self.level = level
338 self.pos = (0, 0)
[108]339 self.filled_mode = False
[51]340 self.mouse_pos = None
[108]341 self.cur_poly = None
[167]342 self._mouse_drag = False
[199]343 self._draw_objects = False
344 self._draw_enemies = False
[204]345 self._draw_lines = False
[275]346 self.sel_mode = False
[205]347 self._start_pos = None
[51]348
[108]349 def _level_coordinates(self, pos):
350 # Move positions to level values
351 if not pos:
352 return (0, 0)
353 return pos[0] + self.pos[0], pos[1] + self.pos[1]
354
355 def _move_view(self, offset):
[51]356 new_pos = [self.pos[0] + offset[0], self.pos[1] + offset[1]]
357 if new_pos[0] < 0:
358 new_pos[0] = self.pos[0]
359 elif new_pos[0] > self.level.x - SCREEN[0]:
360 new_pos[0] = self.pos[0]
361 if new_pos[1] < 0:
362 new_pos[1] = self.pos[1]
363 elif new_pos[1] > self.level.y - SCREEN[1]:
364 new_pos[1] = self.pos[1]
365 self.pos = tuple(new_pos)
366
[199]367 def set_objects(self, value):
368 if self._draw_objects != value:
369 self._draw_objects = value
370 self.invalidate()
371
372 def set_enemies(self, value):
373 if self._draw_enemies != value:
374 self._draw_enemies = value
375 self.invalidate()
376
[108]377 def draw(self, surface):
[51]378 if (self.cur_poly is not None and self.cur_poly in self.level.polygons
379 and len(self.level.polygons[self.cur_poly])):
380 # We have an active polygon
381 mouse_pos = self._level_coordinates(self.mouse_pos)
[205]382 elif self._draw_lines:
383 # Interior wall mode
384 mouse_pos = self._level_coordinates(self.mouse_pos)
[51]385 else:
386 mouse_pos = None
[205]387 level_surface = level.draw(mouse_pos, self.cur_poly, self.filled_mode,
[206]388 self._draw_lines, self._start_pos)
[199]389 if self._draw_objects:
390 for thing in self.level.drawables:
[251]391 if not isinstance(thing, ne.Enemy):
[199]392 thing.render(level_surface)
393 if self._draw_enemies:
394 for thing in self.level.drawables:
[251]395 if isinstance(thing, ne.Enemy):
[199]396 thing.render(level_surface)
397 surface_area = pygame.rect.Rect(self.pos, SCREEN)
398 surface.blit(level_surface, (0, 0), surface_area)
[51]399
[115]400 def change_poly(self, new_poly):
401 self.cur_poly = new_poly
[204]402 self._draw_lines = False
[115]403 if self.cur_poly is not None:
404 self.filled_mode = False
405
[204]406 def line_mode(self):
407 self.cur_poly = None
408 self._draw_lines = True
409 self.filled_mode = False
[205]410 self._start_pos = None
[204]411
[108]412 def key_down(self, ev):
413 if ev.key == pgl.K_LEFT:
414 self._move_view((-10, 0))
415 elif ev.key == pgl.K_RIGHT:
416 self._move_view((10, 0))
417 elif ev.key == pgl.K_UP:
418 self._move_view((0, -10))
419 elif ev.key == pgl.K_DOWN:
420 self._move_view((0, 10))
[115]421 elif ev.key in (pgl.K_1, pgl.K_2, pgl.K_3, pgl.K_4, pgl.K_5, pgl.K_6):
422 self.change_poly(ev.key - pgl.K_0)
[108]423 elif ev.key == pgl.K_0:
[115]424 self.change_poly(None)
[108]425 elif ev.key == pgl.K_d and self.cur_poly:
426 self.level.delete_point(self.cur_poly)
427 elif ev.key == pgl.K_f:
[117]428 self.set_filled()
[135]429 elif ev.key == pgl.K_c:
430 self.close_poly()
[117]431
432 def set_filled(self):
[165]433 closed, _ = self.level.all_closed()
434 if closed:
[117]435 self.cur_poly = None
436 self.filled_mode = True
[204]437 self._draw_lines = False
[117]438 else:
[165]439 alert('Not all polygons closed, so not filling')
[108]440
[109]441 def mouse_move(self, ev):
442 old_pos = self.mouse_pos
443 self.mouse_pos = ev.pos
[205]444 if old_pos != self.mouse_pos and (self.cur_poly or self._draw_lines):
[109]445 self.invalidate()
446
[167]447 def mouse_drag(self, ev):
448 if self._mouse_drag:
449 old_pos = self.mouse_pos
450 self.mouse_pos = ev.pos
451 diff = (-self.mouse_pos[0] + old_pos[0],
452 -self.mouse_pos[1] + old_pos[1])
453 self._move_view(diff)
454 self.invalidate()
455
[109]456 def mouse_down(self, ev):
[275]457 if self.sel_mode and ev.button == 1:
458 corrected_pos = ev.pos[0] + self.pos[0], ev.pos[1] + self.pos[1]
459 obj = self.level.find_obj_at_pos(corrected_pos)
460 if obj is not None:
461 self._edit_selected(obj)
462 elif ev.button == 1:
[204]463 if self._draw_lines:
[205]464 if self._start_pos is None:
465 self._start_pos = ev.pos
466 else:
467 self.level.add_line(self._start_pos, ev.pos)
468 self._start_pos = None
[204]469 else:
470 print "Click: %r" % (
471 self.level.point_to_pymunk(
472 self._level_coordinates(ev.pos)),)
[157]473 if ev.button == 4: # Scroll up
474 self._move_view((0, -10))
475 elif ev.button == 5: # Scroll down
476 self._move_view((0, 10))
477 elif ev.button == 6: # Scroll left
478 self._move_view((-10, 0))
479 elif ev.button == 7: # Scroll right
480 self._move_view((10, 0))
[167]481 elif self.cur_poly and ev.button == 1:
[109]482 # Add a point
483 self.level.add_point(self.cur_poly,
484 self._level_coordinates(ev.pos))
[167]485 elif ev.button == 3:
486 self._mouse_drag = True
487
488 def mouse_up(self, ev):
489 if ev.button == 3:
490 self._mouse_drag = False
[108]491
[135]492 def close_poly(self):
493 if self.cur_poly is None:
494 return
495 if self.level.close_poly(self.cur_poly):
496 alert("Successfully closed the polygon")
497 self.change_poly(None)
498 else:
499 alert("Failed to close the polygon")
500
[251]501 def _edit_class(self, classname, cls, data):
502 # Dialog for class properties
503 dialog = EditClassDialog(classname, cls, data)
504 if dialog.present() == 'OK':
505 return dialog.get_data()
506 return None
507
[275]508 def _edit_selected(self, obj):
509 data = self.level.lookup[obj]
510 cls = obj.__class__
511 classname = obj.__class__.__name__
512 dialog = EditClassDialog(classname, cls, data, True)
513 res = dialog.present()
514 if res == 'OK':
515 edited = dialog.get_data()
516 if edited is not None:
517 for target in [self.level._game_objects, self.level._enemies]:
518 if data in target:
519 self.level.try_new_object(classname, target,
520 edited, data)
521 break
522 elif res == 'Delete':
523 for target in [self.level._game_objects, self.level._enemies]:
524 if data in target:
525 target.remove(data)
526 self.level.reset_objs()
527 break
528
[225]529 def _make_edit_dialog(self, entries):
530 # Dialog to hold the editor
531 edit_box = Dialog()
532 edit_box.rect = pygame.rect.Rect(0, 0, 700, 500)
533 table = ObjectTable(entries)
534 edit_box.add(table)
535 buttons = []
536 for text in ['OK', 'Delete', 'Cancel']:
537 but = Button(text, action=lambda x=text: edit_box.dismiss(x))
538 buttons.append(but)
539 row = Row(buttons)
[251]540 row.rect = pygame.rect.Rect(250, 450, 700, 50)
[225]541 edit_box.add(row)
[236]542 edit_box.get_selection = lambda: table.get_selection()
[225]543 return edit_box
544
545 def edit_objects(self):
546 edit_box = self._make_edit_dialog(self.level._game_objects)
547 res = edit_box.present()
[236]548 choice = edit_box.get_selection()
549 if choice is None:
550 return
[225]551 if res == 'OK':
[251]552 cls = self.level.get_class(choice['classname'])
553 edited = self._edit_class(choice['classname'], cls, choice)
554 if edited is not None:
[262]555 self.level.try_new_object(choice["classname"],
556 self.level._game_objects,
557 edited, choice)
[225]558 elif res == 'Delete':
[239]559 self.level._game_objects.remove(choice)
560 self.level.reset_objs()
[225]561
562 def edit_enemies(self):
563 edit_box = self._make_edit_dialog(self.level._enemies)
564 res = edit_box.present()
[236]565 choice = edit_box.get_selection()
566 if choice is None:
567 return
[225]568 if res == 'OK':
[251]569 cls = self.level.get_class(choice['classname'], ne)
570 edited = self._edit_class(choice['classname'], cls, choice)
571 if edited is not None:
[262]572 self.level.try_new_object(choice["classname"],
573 self.level._enemies, edited, choice)
[225]574 elif res == 'Delete':
[239]575 self.level._enemies.remove(choice)
576 self.level.reset_objs()
[225]577
[236]578 def _make_choice_dialog(self, classes):
579 # Dialog to hold the editor
580 data = []
581 for cls_name, cls in classes:
582 data.append({"classname": cls_name, "class": cls})
[239]583 choice_box = Dialog()
584 choice_box.rect = pygame.rect.Rect(0, 0, 700, 500)
[236]585 table = ObjectTable(data)
[239]586 choice_box.add(table)
[236]587 buttons = []
588 for text in ['OK', 'Cancel']:
[239]589 but = Button(text, action=lambda x=text: choice_box.dismiss(x))
[236]590 buttons.append(but)
591 row = Row(buttons)
[251]592 row.rect = pygame.rect.Rect(250, 450, 700, 50)
[239]593 choice_box.add(row)
594 choice_box.get_selection = lambda: table.get_selection()
595 return choice_box
[236]596
597 def add_game_object(self):
[251]598 classes = ngo.get_editable_game_objects()
599 choose = self._make_choice_dialog(classes)
600 res = choose.present()
601 choice = choose.get_selection()
602 if res == 'OK' and choice is not None:
603 classname = choice['classname']
604 cls = choice['class']
605 new_cls = self._edit_class(classname, cls, None)
606 if new_cls is not None:
[262]607 self.level.try_new_object(classname, self.level._game_objects,
608 new_cls, None)
[251]609
610 def add_enemy(self):
611 classes = ne.get_editable_enemies()
[236]612 choose = self._make_choice_dialog(classes)
613 res = choose.present()
614 choice = choose.get_selection()
615 if res == 'OK' and choice is not None:
[251]616 classname = choice['classname']
617 cls = choice['class']
618 new_cls = self._edit_class(classname, cls, None)
619 if new_cls is not None:
[262]620 self.level.try_new_object(classname, self.level._enemies,
621 new_cls, None)
[236]622
[251]623 def add_puzzler(self):
624 classes = np.get_editable_puzzlers()
[236]625 choose = self._make_choice_dialog(classes)
626 res = choose.present()
627 choice = choose.get_selection()
628 if res == 'OK' and choice is not None:
[251]629 classname = choice['classname']
630 cls = choice['class']
631 new_cls = self._edit_class(classname, cls, None)
632 if new_cls is not None:
[262]633 self.level.try_new_object(classname, self.level._game_objects,
634 new_cls, None)
[236]635
[108]636
[115]637class PolyButton(Button):
638 """Button for coosing the correct polygon"""
639
640 def __init__(self, index, level_widget):
641 if index is not None:
642 text = "Draw: %s" % index
643 else:
644 text = 'Exit Draw Mode'
645 super(PolyButton, self).__init__(text)
646 self.index = index
647 self.level_widget = level_widget
648
649 def action(self):
650 self.level_widget.change_poly(self.index)
651
652
[108]653class EditorApp(RootWidget):
654
655 def __init__(self, level, surface):
656 super(EditorApp, self).__init__(surface)
657 self.level = level
658 self.level_widget = LevelWidget(self.level)
659 self.add(self.level_widget)
660
[220]661 self._dMenus = {}
662
663 self._make_draw_menu()
664 self._make_objects_menu()
665
666 self._menu_mode = 'drawing'
667 self._populate_menu()
668
669 def _make_draw_menu(self):
670 widgets = []
671
[115]672 # Add poly buttons
673 y = 15
674 for poly in range(1, 7):
675 but = PolyButton(poly, self.level_widget)
676 but.rect = pygame.rect.Rect(0, 0, MENU_WIDTH // 2 - MENU_PAD,
677 MENU_BUTTON_HEIGHT)
678 if poly % 2:
679 but.rect.move_ip(MENU_LEFT, y)
680 else:
681 but.rect.move_ip(MENU_LEFT + MENU_WIDTH // 2 - MENU_HALF_PAD,
682 y)
683 y += MENU_BUTTON_HEIGHT + MENU_PAD
[220]684 widgets.append(but)
[198]685
[115]686 end_poly_but = PolyButton(None, self.level_widget)
[220]687 end_poly_but.rect = BUTTON_RECT.copy()
[115]688 end_poly_but.rect.move_ip(MENU_LEFT, y)
[220]689 widgets.append(end_poly_but)
[115]690 y += MENU_BUTTON_HEIGHT + MENU_PAD
691
[204]692 draw_line = Button("Draw interior wall", self.level_widget.line_mode)
[220]693 draw_line.rect = BUTTON_RECT.copy()
[204]694 draw_line.rect.move_ip(MENU_LEFT, y)
[220]695 widgets.append(draw_line)
[204]696 y += MENU_BUTTON_HEIGHT + MENU_PAD
697
[117]698 fill_but = Button('Fill exterior', action=self.level_widget.set_filled)
[220]699 fill_but.rect = BUTTON_RECT.copy()
[117]700 fill_but.rect.move_ip(MENU_LEFT, y)
[220]701 widgets.append(fill_but)
[117]702 y += MENU_BUTTON_HEIGHT + MENU_PAD
703
[135]704 close_poly_but = Button('Close Polygon',
705 action=self.level_widget.close_poly)
[220]706 close_poly_but.rect = BUTTON_RECT.copy()
[135]707 close_poly_but.rect.move_ip(MENU_LEFT, y)
[220]708 widgets.append(close_poly_but)
[135]709 y += MENU_BUTTON_HEIGHT + MENU_PAD
710
[198]711 white = pygame.color.Color("white")
712 self.show_objs = CheckBox(fg_color=white)
[220]713 self.show_objs.rect = CHECK_RECT.copy()
[198]714 self.show_objs.rect.move_ip(MENU_LEFT, y)
715 label = Label("Show Objects", fg_color=white)
716 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
[220]717 widgets.append(self.show_objs)
718 widgets.append(label)
[198]719 y += label.rect.height + MENU_PAD
720
721 self.show_enemies = CheckBox(fg_color=white)
[220]722 self.show_enemies.rect = CHECK_RECT.copy()
[198]723 self.show_enemies.rect.move_ip(MENU_LEFT, y)
724 label = Label("Show enemy start pos", fg_color=white)
725 label.rect.move_ip(MENU_LEFT + MENU_BUTTON_HEIGHT // 2 + MENU_PAD, y)
[220]726 widgets.append(self.show_enemies)
727 widgets.append(label)
[198]728 y += label.rect.height + MENU_PAD
729
[236]730 y += MENU_PAD
[220]731 switch_but = Button('Switch to Objects', action=self.switch_to_objects)
732 switch_but.rect = BUTTON_RECT.copy()
733 switch_but.rect.move_ip(MENU_LEFT, y)
734 widgets.append(switch_but)
735 y += switch_but.rect.height + MENU_PAD
736
[236]737 save_but = Button('Save Level', action=self.save)
738 save_but.rect = BUTTON_RECT.copy()
739 save_but.rect.move_ip(MENU_LEFT, y)
740 widgets.append(save_but)
741 y += MENU_BUTTON_HEIGHT + MENU_PAD
742
743 y += MENU_PAD
[108]744 quit_but = Button('Quit', action=self.quit)
[220]745 quit_but.rect = BUTTON_RECT.copy()
[115]746 quit_but.rect.move_ip(MENU_LEFT, y)
[220]747 widgets.append(quit_but)
748
749 self._dMenus['drawing'] = widgets
750
751 def _make_objects_menu(self):
752 widgets = []
753
754 # Add poly buttons
755 y = 15
756
[225]757 edit_objs_but = Button('Edit Objects',
758 action=self.level_widget.edit_objects)
759 edit_objs_but.rect = BUTTON_RECT.copy()
760 edit_objs_but.rect.move_ip(MENU_LEFT, y)
761 widgets.append(edit_objs_but)
762 y += MENU_BUTTON_HEIGHT + MENU_PAD
763
764 edir_enemies_but = Button('Edit Enemies',
765 action=self.level_widget.edit_enemies)
766 edir_enemies_but.rect = BUTTON_RECT.copy()
767 edir_enemies_but.rect.move_ip(MENU_LEFT, y)
768 widgets.append(edir_enemies_but)
769 y += MENU_BUTTON_HEIGHT + MENU_PAD
770
[236]771 add_obj_but = Button('Add Game Object',
772 action=self.level_widget.add_game_object)
773 add_obj_but.rect = BUTTON_RECT.copy()
774 add_obj_but.rect.move_ip(MENU_LEFT, y)
775 widgets.append(add_obj_but)
776 y += MENU_BUTTON_HEIGHT + MENU_PAD
777
778 add_puzzle_but = Button('Add Puzzler',
779 action=self.level_widget.add_puzzler)
780 add_puzzle_but.rect = BUTTON_RECT.copy()
781 add_puzzle_but.rect.move_ip(MENU_LEFT, y)
782 widgets.append(add_puzzle_but)
783 y += MENU_BUTTON_HEIGHT + MENU_PAD
784
785 add_enemy_but = Button('Add Enemy',
786 action=self.level_widget.add_enemy)
787 add_enemy_but.rect = BUTTON_RECT.copy()
788 add_enemy_but.rect.move_ip(MENU_LEFT, y)
789 widgets.append(add_enemy_but)
790 y += MENU_BUTTON_HEIGHT + MENU_PAD
791
792 y += MENU_PAD
[275]793 sel_mode_but = Button('Select Object',
794 action=self.sel_mode)
795 sel_mode_but.rect = BUTTON_RECT.copy()
796 sel_mode_but.rect.move_ip(MENU_LEFT, y)
797 widgets.append(sel_mode_but)
798 y += MENU_BUTTON_HEIGHT + MENU_PAD
799
800 y += MENU_PAD
[236]801 switch_but = Button('Switch to Drawing', action=self.switch_to_draw)
802 switch_but.rect = BUTTON_RECT.copy()
803 switch_but.rect.move_ip(MENU_LEFT, y)
804 widgets.append(switch_but)
805 y += switch_but.rect.height + MENU_PAD
806
[220]807 save_but = Button('Save Level', action=self.save)
808 save_but.rect = BUTTON_RECT.copy()
809 save_but.rect.move_ip(MENU_LEFT, y)
810 widgets.append(save_but)
811 y += MENU_BUTTON_HEIGHT + MENU_PAD
812
[236]813 y += MENU_PAD
[220]814 quit_but = Button('Quit', action=self.quit)
815 quit_but.rect = BUTTON_RECT.copy()
816 quit_but.rect.move_ip(MENU_LEFT, y)
817 widgets.append(quit_but)
818
819 self._dMenus['objects'] = widgets
[108]820
821 def key_down(self, ev):
822 if ev.key == pgl.K_ESCAPE:
823 self.quit()
[116]824 elif ev.key == pgl.K_s:
825 self.save()
[108]826 else:
827 self.level_widget.key_down(ev)
[51]828
[116]829 def save(self):
[122]830 closed, messages = self.level.all_closed()
831 if closed:
832 self.level.save()
833 # display success
834 alert("Level %s saved successfully." % self.level.name)
835 else:
836 # display errors
837 alert("Failed to save level.\n\n%s" % '\n'.join(messages))
[116]838
[220]839 def switch_to_draw(self):
840 if self._menu_mode != 'drawing':
841 self._clear_menu()
842 self._menu_mode = 'drawing'
843 self._populate_menu()
844
845 def switch_to_objects(self):
846 if self._menu_mode != 'objects':
847 self._clear_menu()
848 self._menu_mode = 'objects'
849 self._populate_menu()
850
851 def _clear_menu(self):
852 for widget in self._dMenus[self._menu_mode]:
853 self.remove(widget)
854
855 def _populate_menu(self):
856 self.level_widget.change_poly(None)
[275]857 self.level_widget.sel_mode = False
[220]858 for widget in self._dMenus[self._menu_mode]:
859 self.add(widget)
860 self.invalidate()
861
[275]862 def sel_mode(self):
863 self.level_widget.sel_mode = not self.level_widget.sel_mode
864
[109]865 def mouse_move(self, ev):
866 self.level_widget.mouse_move(ev)
867
[199]868 def draw(self, surface):
869 # Update checkbox state
[223]870 if self._menu_mode == 'drawing':
871 self.level_widget.set_objects(self.show_objs.value)
872 self.level_widget.set_enemies(self.show_enemies.value)
873 else:
874 self.level_widget.set_objects(True)
875 self.level_widget.set_enemies(True)
[199]876 super(EditorApp, self).draw(surface)
877
[51]878
879if __name__ == "__main__":
[195]880 if len(sys.argv) not in [2, 4]:
[51]881 print 'Please supply a levelname or levelname and level size'
882 sys.exit()
[199]883 # Need to ensure we have defaults for rendering
884 parse_args([])
[51]885 pygame.display.init()
886 pygame.font.init()
[108]887 pygame.display.set_mode((SCREEN[0] + MENU_WIDTH, SCREEN[1]),
[109]888 pgl.SWSURFACE)
[195]889 if len(sys.argv) == 2:
890 level = EditorLevel(sys.argv[1])
891 level.load(pymunk.Space())
892 elif len(sys.argv) == 4:
893 level = EditorLevel(sys.argv[1], int(sys.argv[2]), int(sys.argv[3]))
[51]894 pygame.display.set_caption('Nagslang Area Editor')
[108]895 pygame.key.set_repeat(200, 100)
896 app = EditorApp(level, pygame.display.get_surface())
897 app.run()
Note: See TracBrowser for help on using the repository browser.