comparison pyntnclick/tools/rect_drawer.py @ 589:ebc48b397fd5 pyntnclick

Turn rect_drawer into a command line option
author Neil Muller <neil@dip.sun.ac.za>
date Sat, 11 Feb 2012 17:07:52 +0200
parents tools/rect_drawer.py@1b1ab71535bd
children 2748d3afcae5
comparison
equal deleted inserted replaced
588:0571deb177e9 589:ebc48b397fd5
1 # Quickly hacked together helper for working out
2 # interactive regions in Suspended Sentence
3
4 import sys
5 import os.path
6
7 script_path = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
8 sys.path.append(script_path)
9
10 from albow.root import RootWidget
11 from albow.utils import frame_rect
12 from albow.widget import Widget
13 from albow.controls import Button, Image
14 from albow.palette_view import PaletteView
15 from albow.file_dialogs import request_old_filename
16 from albow.resource import get_font
17 from pygame.locals import (K_LEFT, K_RIGHT, K_UP, K_DOWN,
18 K_a, K_t, K_d, K_i, K_r, K_o, K_b, K_z,
19 BLEND_RGBA_MIN, SRCALPHA)
20 import pygame
21
22 import pyntnclick.constants
23 from pyntnclick import state
24 state.DEBUG_RECTS = True
25 from pyntnclick.widgets import BoomLabel
26
27
28 class RectDrawerConstants(pyntnclick.constants.GameConstants):
29 debug = True
30 menu_width = 200
31 menu_button_height = 25
32 zoom = 4
33 zoom_step = 100
34
35 constants = RectDrawerConstants()
36
37
38 class AppPalette(PaletteView):
39
40 sel_width = 5
41
42 colors = [
43 'red', 'maroon1', 'palevioletred1', 'moccasin', 'orange',
44 'honeydew', 'yellow', 'gold', 'goldenrod', 'brown',
45 'blue', 'purple', 'darkorchid4', 'thistle', 'skyblue1',
46 'green', 'palegreen1', 'darkgreen', 'aquamarine', 'darkolivegreen',
47 ]
48
49 def __init__(self, app_image):
50 self.image = app_image
51 super(AppPalette, self).__init__((35, 35), 4, 5, margin=2)
52 self.selection = 0
53 self.image.rect_color = pygame.color.Color(self.colors[self.selection])
54
55 def num_items(self):
56 return len(self.colors)
57
58 def draw_item(self, surface, item_no, rect):
59 d = -2 * self.sel_width
60 r = rect.inflate(d, d)
61 surface.fill(pygame.color.Color(self.colors[item_no]), r)
62
63 def click_item(self, item_no, event):
64 self.selection = item_no
65 self.image.rect_color = pygame.color.Color(self.colors[item_no])
66
67 def item_is_selected(self, item_no):
68 return self.selection == item_no
69
70
71 class AppImage(Widget):
72
73 rect_thick = 3
74 draw_thick = 1
75
76 def __init__(self, state):
77 self.state = state
78 super(AppImage, self).__init__(pygame.rect.Rect(0, 0,
79 constants.screen[0],
80 constants.screen[1]))
81 self.mode = 'draw'
82 self.rects = []
83 self.images = []
84 self.start_pos = None
85 self.end_pos = None
86 self.rect_color = pygame.color.Color('white')
87 self.current_image = None
88 self.place_image_menu = None
89 self.close_button = BoomLabel('Close', font=get_font(20, 'Vera.ttf'))
90 self.close_button.fg_color = (0, 0, 0)
91 self.close_button.bg_color = (0, 0, 0)
92 self.draw_rects = True
93 self.draw_things = True
94 self.draw_thing_rects = True
95 self.draw_images = True
96 self.trans_images = False
97 self.draw_toolbar = True
98 self.old_mouse_pos = None
99 self.zoom_display = False
100 self.draw_anim = False
101 self.zoom_offset = (600, 600)
102 if self.state.current_detail:
103 w, h = self.state.current_detail.get_detail_size()
104 rect = pygame.rect.Rect(0, 0, w, h)
105 self.close_button.rect.midbottom = rect.midbottom
106 self.offset = (0, 0)
107 else:
108 self.offset = (-self.state.current_scene.OFFSET[0],
109 -self.state.current_scene.OFFSET[1])
110 self.find_existing_intersects()
111
112 def _get_scene(self):
113 if self.state.current_detail:
114 return self.state.current_detail
115 else:
116 return self.state.current_scene
117
118 def find_existing_intersects(self):
119 """Parse the things in the scene for overlaps"""
120 scene = self._get_scene()
121 # Pylint hates this function
122 for thing in scene.things.itervalues():
123 for interact_name in thing.interacts:
124 thing.set_interact(interact_name)
125 if hasattr(thing.rect, 'collidepoint'):
126 thing_rects = [thing.rect]
127 else:
128 thing_rects = thing.rect
129 for thing2 in scene.things.itervalues():
130 if thing is thing2:
131 continue
132 for interact2_name in thing2.interacts:
133 thing2.set_interact(interact2_name)
134 if hasattr(thing2.rect, 'collidepoint'):
135 thing2_rects = [thing2.rect]
136 else:
137 thing2_rects = thing2.rect
138 for my_rect in thing_rects:
139 for other_rect in thing2_rects:
140 if my_rect.colliderect(other_rect):
141 print 'Existing Intersecting rects'
142 print (" Thing1 %s Interact %s"
143 % (thing.name, interact_name))
144 print (" Thing2 %s Interact %s"
145 % (thing2.name, interact2_name))
146 print " Rects", my_rect, other_rect
147 print
148
149 def find_intersecting_rects(self, d):
150 """Find if any rect collections intersect"""
151 # I loath N^X brute search algorithm's, but whatever, hey
152 scene = self._get_scene()
153 for (num, col) in enumerate(d):
154 rect_list = d[col]
155 for thing in scene.things.itervalues():
156 for interact_name in thing.interacts:
157 thing.set_interact(interact_name)
158 if hasattr(thing.rect, 'collidepoint'):
159 thing_rects = [thing.rect]
160 else:
161 thing_rects = thing.rect
162 for other_rect in thing_rects:
163 for my_rect in rect_list:
164 if my_rect.colliderect(other_rect):
165 print 'Intersecting rects'
166 print " Object %s" % num
167 print (" Thing %s Interact %s"
168 % (thing.name, interact_name))
169 print " Rects", my_rect, other_rect
170 if thing.INITIAL:
171 thing.set_interact(thing.INITIAL)
172 print
173 for (num2, col2) in enumerate(d):
174 if num2 == num:
175 continue
176 other_list = d[col2]
177 for my_rect in rect_list:
178 for other_rect in other_list:
179 if my_rect.colliderect(other_rect):
180 print 'Intersecting rects',
181 print ' Object %s and %s' % (num, num2)
182 print " Rects", my_rect, other_rect
183 print
184 print
185
186 def toggle_things(self):
187 self.draw_things = not self.draw_things
188
189 def toggle_thing_rects(self):
190 self.draw_thing_rects = not self.draw_thing_rects
191 scene = self._get_scene()
192 for thing in scene.things.itervalues():
193 if not self.draw_thing_rects:
194 if not hasattr(thing, 'old_colour'):
195 thing.old_colour = thing._interact_hilight_color
196 thing._interact_hilight_color = None
197 else:
198 thing._interact_hilight_color = thing.old_colour
199
200 def toggle_images(self):
201 self.draw_images = not self.draw_images
202
203 def toggle_trans_images(self):
204 self.trans_images = not self.trans_images
205 self.invalidate()
206
207 def toggle_rects(self):
208 self.draw_rects = not self.draw_rects
209
210 def toggle_toolbar(self):
211 self.draw_toolbar = not self.draw_toolbar
212
213 def toggle_zoom(self):
214 self.zoom_display = not self.zoom_display
215
216 def toggle_anim(self):
217 self.draw_anim = not self.draw_anim
218
219 def draw_mode(self):
220 self.mode = 'draw'
221
222 def del_mode(self):
223 self.mode = 'del'
224 self.start_pos = None
225 self.end_pos = None
226
227 def draw_sub_image(self, image, surface, cropped_rect):
228 """Tweaked image drawing to avoid albow's centring the image in the
229 subsurface"""
230 surf = pygame.surface.Surface((cropped_rect.w, cropped_rect.h),
231 SRCALPHA).convert_alpha()
232 frame = surf.get_rect()
233 imsurf = image.get_image().convert_alpha()
234 r = imsurf.get_rect()
235 r.topleft = frame.topleft
236 if self.trans_images:
237 surf.fill(pygame.color.Color(255, 255, 255, 96))
238 surf.blit(imsurf, r, None, BLEND_RGBA_MIN)
239 else:
240 surf.blit(imsurf, r, None)
241 surface.blit(surf, cropped_rect)
242
243 def draw(self, surface):
244 if self.zoom_display:
245 base_surface = surface.copy()
246 self.do_unzoomed_draw(base_surface)
247 zoomed = pygame.transform.scale(base_surface,
248 (constants.zoom * constants.screen[0],
249 constants.zoom * constants.screen[1]))
250 area = pygame.rect.Rect(self.zoom_offset[0], self.zoom_offset[1],
251 self.zoom_offset[0] + constants.screen[0],
252 self.zoom_offset[1] + constants.screen[1])
253 surface.blit(zoomed, (0, 0), area)
254 else:
255 self.do_unzoomed_draw(surface)
256
257 def do_unzoomed_draw(self, surface):
258 if self.state.current_detail:
259 if self.draw_things:
260 self.state.current_detail.draw(surface, None)
261 else:
262 self.state.current_detail.draw_background(surface)
263 # We duplicate Albow's draw logic here, so we zoom the close
264 # button correctly
265 r = self.close_button.get_rect()
266 surf_rect = surface.get_rect()
267 sub_rect = surf_rect.clip(r)
268 try:
269 sub = surface.subsurface(sub_rect)
270 self.close_button.draw_all(sub)
271 except ValueError, e:
272 print 'Error, failed to draw close button', e
273 else:
274 if self.draw_things:
275 self.state.current_scene.draw(surface, None)
276 else:
277 self.state.current_scene.draw_background(surface)
278 if self.mode == 'draw' and self.start_pos and self.draw_rects:
279 rect = pygame.rect.Rect(self.start_pos[0], self.start_pos[1],
280 self.end_pos[0] - self.start_pos[0],
281 self.end_pos[1] - self.start_pos[1])
282 rect.normalize()
283 frame_rect(surface, self.rect_color, rect, self.draw_thick)
284 if self.draw_rects:
285 for (col, rect) in self.rects:
286 frame_rect(surface, col, rect, self.rect_thick)
287 if self.draw_images:
288 for image in self.images:
289 if image.rect.colliderect(surface.get_rect()):
290 cropped_rect = image.rect.clip(surface.get_rect())
291 self.draw_sub_image(image, surface, cropped_rect)
292 else:
293 print 'image outside surface', image
294 if self.current_image and self.mode == 'image':
295 if self.current_image.rect.colliderect(surface.get_rect()):
296 cropped_rect = self.current_image.rect.clip(
297 surface.get_rect())
298 self.draw_sub_image(self.current_image, surface,
299 cropped_rect)
300 if self.draw_toolbar:
301 tb_surf = surface.subsurface(0, constants.screen[1]
302 - constants.button_size,
303 constants.screen[0],
304 constants.button_size).convert_alpha()
305 tb_surf.fill(pygame.color.Color(127, 0, 0, 191))
306 surface.blit(tb_surf, (0, constants.screen[1]
307 - constants.button_size))
308
309 def _make_dict(self):
310 d = {}
311 for col, rect in self.rects:
312 col = (col.r, col.g, col.b)
313 d.setdefault(col, [])
314 d[col].append(rect)
315 return d
316
317 def print_objs(self):
318 d = self._make_dict()
319 self.find_intersecting_rects(d)
320 for (num, col) in enumerate(d):
321 print 'Rect %d : ' % num
322 for rect in d[col]:
323 r = rect.move(self.offset)
324 print ' (%d, %d, %d, %d),' % (r.x, r.y, r.w, r.h)
325 print
326 for i, image in enumerate(self.images):
327 print 'Image %d' % i
328 rect = image.rect
329 r = rect.move(self.offset)
330 print ' (%d, %d, %d, %d),' % (r.x, r.y, r.w, r.h)
331 print
332 print
333
334 def image_load(self):
335 image_path = ('%s/Resources/images/%s'
336 % (script_path, self.state.current_scene.FOLDER))
337 imagename = request_old_filename(directory=image_path)
338 try:
339 image_data = pygame.image.load(imagename)
340 self.current_image = Image(image_data)
341 self.place_image_menu.enabled = True
342 # ensure we're off screen to start
343 self.current_image.rect = image_data.get_rect() \
344 .move(constants.screen[0] + constants.menu_width,
345 constants.screen[1])
346 except pygame.error, e:
347 print 'Unable to load image %s' % e
348
349 def image_mode(self):
350 self.mode = 'image'
351 self.start_pos = None
352 self.end_pos = None
353 # So we do the right thing for off screen images
354 self.old_mouse_pos = None
355
356 def cycle_mode(self):
357 self.mode = 'cycle'
358
359 def _conv_pos(self, mouse_pos):
360 if self.zoom_display:
361 pos = ((mouse_pos[0] + self.zoom_offset[0]) / constants.zoom,
362 (mouse_pos[1] + self.zoom_offset[1]) / constants.zoom)
363 else:
364 pos = mouse_pos
365 return pos
366
367 def _check_limits(self, offset):
368 if offset[0] < 0:
369 offset[0] = 0
370 if offset[1] < 0:
371 offset[1] = 0
372 width, height = constants.screen
373 if offset[0] > constants.zoom * width - width:
374 offset[0] = constants.zoom * width - width
375 if offset[1] > constants.zoom * height - height:
376 offset[1] = constants.zoom * height - height
377
378 def _make_zoom_offset(self, pos):
379 zoom_pos = (pos[0] * constants.zoom, pos[1] * constants.zoom)
380 offset = [zoom_pos[0] - constants.screen[0] / 2,
381 zoom_pos[1] - constants.screen[1] / 2]
382 self._check_limits(offset)
383 self.zoom_offset = tuple(offset)
384
385 def _move_zoom(self, x, y):
386 offset = list(self.zoom_offset)
387 offset[0] += constants.zoom_step * x
388 offset[1] += constants.zoom_step * y
389 self._check_limits(offset)
390 self.zoom_offset = tuple(offset)
391
392 def do_mouse_move(self, e):
393 pos = self._conv_pos(e.pos)
394 if not self.zoom_display:
395 # Construct zoom offset from mouse pos
396 self._make_zoom_offset(e.pos)
397 if self.mode == 'image' and self.current_image:
398 if self.old_mouse_pos:
399 delta = (pos[0] - self.old_mouse_pos[0],
400 pos[1] - self.old_mouse_pos[1])
401 self.current_image.rect.center = (
402 self.current_image.rect.center[0] + delta[0],
403 self.current_image.rect.center[1] + delta[1])
404 else:
405 self.current_image.rect.center = pos
406 self.invalidate()
407 self.old_mouse_pos = pos
408
409 def key_down(self, e):
410 if self.mode == 'image' and self.current_image:
411 # Move the image by 1 pixel
412 cur_pos = self.current_image.rect.center
413 if e.key == K_LEFT:
414 self.current_image.rect.center = (cur_pos[0] - 1, cur_pos[1])
415 elif e.key == K_RIGHT:
416 self.current_image.rect.center = (cur_pos[0] + 1, cur_pos[1])
417 elif e.key == K_UP:
418 self.current_image.rect.center = (cur_pos[0], cur_pos[1] - 1)
419 elif e.key == K_DOWN:
420 self.current_image.rect.center = (cur_pos[0], cur_pos[1] + 1)
421 elif self.zoom_display:
422 if e.key == K_LEFT:
423 self._move_zoom(-1, 0)
424 elif e.key == K_RIGHT:
425 self._move_zoom(1, 0)
426 elif e.key == K_UP:
427 self._move_zoom(0, -1)
428 elif e.key == K_DOWN:
429 self._move_zoom(0, 1)
430
431 if e.key == K_o:
432 self.toggle_trans_images()
433 elif e.key == K_t:
434 self.toggle_things()
435 elif e.key == K_r:
436 self.toggle_thing_rects()
437 elif e.key == K_i:
438 self.toggle_images()
439 elif e.key == K_d:
440 self.toggle_rects()
441 elif e.key == K_b:
442 self.toggle_toolbar()
443 elif e.key == K_z:
444 self.toggle_zoom()
445 elif e.key == K_a:
446 self.toggle_anim()
447
448 def mouse_down(self, e):
449 pos = self._conv_pos(e.pos)
450 if self.mode == 'del':
451 cand = None
452 # Images are drawn above rectangles, so search those first
453 for image in self.images:
454 if image.rect.collidepoint(pos):
455 cand = image
456 break
457 if cand:
458 self.images.remove(cand)
459 self.invalidate()
460 return
461 for (col, rect) in self.rects:
462 if rect.collidepoint(pos):
463 cand = (col, rect)
464 break
465 if cand:
466 self.rects.remove(cand)
467 self.invalidate()
468 elif self.mode == 'cycle':
469 scene = self._get_scene()
470 cand = None
471 for thing in scene.things.itervalues():
472 if thing.contains(pos):
473 cand = thing
474 break
475 if cand:
476 # Find current interacts in this thing
477 cur_interact = cand.current_interact
478 j = cand.interacts.values().index(cur_interact)
479 if j + 1 < len(cand.interacts):
480 next_name = cand.interacts.keys()[j + 1]
481 else:
482 next_name = cand.interacts.keys()[0]
483 if cand.interacts[next_name] != cur_interact:
484 cand.set_interact(next_name)
485 elif self.mode == 'draw':
486 self.start_pos = pos
487 self.end_pos = pos
488 elif self.mode == 'image':
489 if self.current_image:
490 self.images.append(self.current_image)
491 self.current_image = None
492 self.old_mouse_pos = None
493 self.invalidate()
494 else:
495 cand = None
496 for image in self.images:
497 if image.rect.collidepoint(e.pos):
498 cand = image
499 break
500 if cand:
501 self.images.remove(cand)
502 self.current_image = cand
503 # We want to move relative to the current mouse pos, so
504 self.old_mouse_pos = pos
505 self.invalidate()
506
507 def mouse_up(self, e):
508 if self.mode == 'draw':
509 rect = pygame.rect.Rect(self.start_pos[0], self.start_pos[1],
510 self.end_pos[0] - self.start_pos[0],
511 self.end_pos[1] - self.start_pos[1])
512 rect.normalize()
513 self.rects.append((self.rect_color, rect))
514 self.start_pos = self.end_pos = None
515
516 def mouse_drag(self, e):
517 if self.mode == 'draw':
518 self.end_pos = self._conv_pos(e.pos)
519 self.invalidate()
520
521 def animate(self):
522 if self.draw_anim:
523 if self.state.animate():
524 self.invalidate()
525
526
527 class ModeLabel(BoomLabel):
528
529 def __init__(self, app_image):
530 self.app_image = app_image
531 super(ModeLabel, self).__init__('Mode : ', 200,
532 font=get_font(15, 'VeraBd.ttf'),
533 fg_color=pygame.color.Color(128, 0, 255))
534 self.rect.move_ip(805, 0)
535
536 def draw_all(self, surface):
537 self.set_text('Mode : %s' % self.app_image.mode)
538 super(ModeLabel, self).draw_all(surface)
539
540
541 def make_button(text, action, ypos):
542 button = Button(text, action=action, font=get_font(15, 'VeraBd.ttf'))
543 button.align = 'l'
544 button.rect = pygame.rect.Rect(0, 0, constants.menu_width,
545 constants.menu_button_height)
546 button.rect.move_ip(805, ypos)
547 return button
548
549
550 class RectApp(RootWidget):
551 """Handle the app stuff for the rect drawer"""
552
553 def __init__(self, display, get_initial_state, scene, detail):
554 super(RectApp, self).__init__(display)
555 pygame.key.set_repeat(200, 100)
556 state = get_initial_state()
557 state.set_current_scene(scene)
558 state.set_current_detail(detail)
559 state.do_check = None
560
561 self.image = AppImage(state)
562 self.add(self.image)
563 mode_label = ModeLabel(self.image)
564 self.add(mode_label)
565 y = mode_label.get_rect().h
566 draw = make_button('Draw Rect', self.image.draw_mode, y)
567 self.add(draw)
568 y += draw.get_rect().h
569 load_image = make_button("Load image", self.image.image_load, y)
570 self.add(load_image)
571 y += load_image.get_rect().h
572 add_image = make_button("Place/Move images", self.image.image_mode, y)
573 add_image.enabled = False
574 self.add(add_image)
575 self.image.place_image_menu = add_image
576 y += add_image.get_rect().h
577 cycle = make_button("Cycle interacts", self.image.cycle_mode, y)
578 self.add(cycle)
579 y += cycle.get_rect().h
580 delete = make_button('Delete Objects', self.image.del_mode, y)
581 self.add(delete)
582 y += delete.get_rect().h
583 palette = AppPalette(self.image)
584 palette.rect.move_ip(810, y)
585 self.add(palette)
586 y += palette.get_rect().h
587 print_rects = make_button("Print objects", self.image.print_objs, y)
588 self.add(print_rects)
589 y += print_rects.get_rect().h
590 toggle_things = make_button("Show Things (t)",
591 self.image.toggle_things, y)
592 self.add(toggle_things)
593 y += toggle_things.get_rect().h
594 toggle_thing_rects = make_button("Show Thing Rects (r)",
595 self.image.toggle_thing_rects, y)
596 self.add(toggle_thing_rects)
597 y += toggle_thing_rects.get_rect().h
598 toggle_images = make_button("Show Images (i)",
599 self.image.toggle_images, y)
600 self.add(toggle_images)
601 y += toggle_images.get_rect().h
602 trans_images = make_button("Opaque Images (o)",
603 self.image.toggle_trans_images, y)
604 self.add(trans_images)
605 y += trans_images.get_rect().h
606 toggle_rects = make_button("Show Drawn Rects (d)",
607 self.image.toggle_rects, y)
608 self.add(toggle_rects)
609 y += toggle_rects.get_rect().h
610 toggle_toolbar = make_button("Show Toolbar (b)",
611 self.image.toggle_toolbar, y)
612 self.add(toggle_toolbar)
613 y += toggle_toolbar.get_rect().h
614 toggle_anim = make_button("Show Animations (a)",
615 self.image.toggle_anim, y)
616 self.add(toggle_anim)
617 y += toggle_anim.get_rect().h
618 toggle_zoom = make_button("Zoom (z)", self.image.toggle_zoom, y)
619 self.add(toggle_zoom)
620 y += toggle_zoom.get_rect().h
621 quit_but = make_button("Quit", self.quit, 570)
622 self.add(quit_but)
623 self.set_timer(constants.frame_rate)
624
625 def key_down(self, event):
626 # Dispatch to image widget
627 self.image.key_down(event)
628
629 def mouse_delta(self, event):
630 # We propogate mouse move from here to draw region, so images move
631 # off-screen
632 self.image.do_mouse_move(event)
633
634 def begin_frame(self):
635 self.image.animate()
636
637
638 def make_rect_display():
639 pygame.display.init()
640 pygame.font.init()
641 return pygame.display.set_mode((constants.screen[0]
642 + constants.menu_width,
643 constants.screen[1]))