source: nagslang/render.py @ 521:61e3e5d28a05

Last change on this file since 521:61e3e5d28a05 was 521:61e3e5d28a05, checked in by Stefano Rivera <stefano@…>, 7 years ago

Sligthly pinker ephemeral notes

File size: 9.3 KB
Line 
1import math
2import textwrap
3
4import pygame
5import pygame.locals as pgl
6import pymunk
7
8from nagslang.options import options
9from nagslang.utils import (
10    tile_surface, vec_from_angle, points_to_pygame, extend_line)
11from nagslang.widgets.text import LabelWidget, MultiLineWidget
12
13
14class Renderer(object):
15    def set_game_object(self, game_object):
16        self.game_object = game_object
17
18    def _render_shape(self, surface):
19        shape = self.game_object.get_shape()
20        # Less general that pymunk.pygame_util.draw, but also a lot less noisy.
21        color = getattr(shape, 'color', pygame.color.THECOLORS['lightblue'])
22        # We only explicitly draw Circle and Poly shapes. Everything else we
23        # forward to pymunk.
24        if isinstance(shape, pymunk.Circle):
25            centre = pymunk.pygame_util.to_pygame(shape.body.position, surface)
26            radius = int(shape.radius)
27            pygame.draw.circle(surface, color, centre, radius, 2)
28        elif isinstance(shape, pymunk.Poly):
29            # polygon bounding box
30            points = [pymunk.pygame_util.to_pygame(p, surface)
31                      for p in shape.get_vertices()]
32            pygame.draw.lines(surface, color, True, points, 2)
33        else:
34            pymunk.pygame_util.draw(surface, shape)
35
36    def render(self, surface):
37        if options.shapes:
38            self._render_shape(surface)
39
40    def update(self, seconds):
41        # Used by time animatations to advance the clock
42        pass
43
44
45class NullRenderer(Renderer):
46    def render(self, surface):
47        pass
48
49
50class BulkheadRenderer(Renderer):
51    def draw_bulkhead_line(self, surface, a, b):
52        ai, bi = extend_line(a, b, -2)
53        a, b, ai, bi = points_to_pygame(surface, (a, b, ai, bi))
54        pygame.draw.line(
55            surface, pygame.color.THECOLORS['black'], a, b, 7)
56        pygame.draw.line(
57            surface, pygame.color.THECOLORS['lightblue'], ai, bi, 5)
58
59    def render(self, surface):
60        shape = self.game_object.get_shape()
61        if self.game_object.puzzler.get_state():
62            offset = vec_from_angle((shape.b - shape.a).angle, 10)
63            ai = shape.a + offset
64            bi = shape.b - offset
65            self.draw_bulkhead_line(surface, shape.a, ai)
66            self.draw_bulkhead_line(surface, bi, shape.b)
67        else:
68            mid = shape.a + (shape.b - shape.a) / 2
69            self.draw_bulkhead_line(surface, shape.a, mid)
70            self.draw_bulkhead_line(surface, mid, shape.b)
71
72
73def image_pos(image, pos):
74    return (pos[0] - image.get_width() / 2,
75            pos[1] - image.get_height() / 2)
76
77
78class ImageRenderer(Renderer):
79    def __init__(self, image):
80        self._image = image
81
82    def get_image(self):
83        return self._image
84
85    def rotate_image(self, image):
86        angle = self.game_object.get_render_angle() * 180 / math.pi
87        return pygame.transform.rotate(image, angle)
88
89    def render_image(self, surface, image):
90        image = self.rotate_image(image)
91        pos = self.game_object.get_render_position(surface)
92        surface.blit(image, image_pos(image, pos))
93
94    def render(self, surface):
95        self.render_image(surface, self.get_image())
96        super(ImageRenderer, self).render(surface)
97
98
99class ImageStateRenderer(ImageRenderer):
100    def __init__(self, state_images):
101        self._state_images = state_images
102
103    def get_image(self):
104        return self._state_images[self.game_object.puzzler.get_state()]
105
106
107class TimedAnimatedRenderer(ImageRenderer):
108    def __init__(self, images, frame_ticks=1):
109        self._images = images
110        self._frame_ticks = frame_ticks
111        self._frame_tick = 0
112        self._frame = 0
113
114    def advance_tick(self):
115        self._frame_tick += 1
116        if self._frame_tick > self._frame_ticks:
117            self._frame_tick = 0
118            self._frame += 1
119        if self._frame >= len(self._images):
120            self._frame = 0
121
122    def reset(self):
123        self._frame_tick = 0
124        self._frame = 0
125
126    def get_image(self):
127        return self._images[self._frame]
128
129    def update(self, seconds):
130        self.advance_tick()
131
132
133class MovementAnimatedRenderer(TimedAnimatedRenderer):
134    def update(self, seconds):
135        if self.game_object.is_moving:
136            self.advance_tick()
137        else:
138            self.reset()
139
140
141class RendererSelectionRenderer(Renderer):
142    def __init__(self, renderers):
143        self._renderers = renderers
144
145    def set_game_object(self, game_object):
146        self.game_object = game_object
147        for renderer in self._renderers.values():
148            renderer.set_game_object(game_object)
149
150    @property
151    def renderer(self):
152        return self._renderers[self.select_renderer()]
153
154    def render(self, surface):
155        return self.renderer.render(surface)
156
157    def update(self, seconds):
158        return self.renderer.update(seconds)
159
160    def select_renderer(self):
161        raise NotImplementedError()
162
163
164class FacingSelectionRenderer(RendererSelectionRenderer):
165    def select_renderer(self):
166        return self.game_object.get_facing_direction()
167
168
169class ShapeRenderer(Renderer):
170    def render(self, surface):
171        self._render_shape(surface)
172        super(ShapeRenderer, self).render(surface)
173
174
175class ShapeStateRenderer(ShapeRenderer):
176    """Renders the shape in a different colour depending on the state.
177
178    Requires the game object it's attached to to have a puzzler.
179    """
180    def render(self, surface):
181        if self.game_object.puzzler.get_state():
182            color = pygame.color.THECOLORS['green']
183        else:
184            color = pygame.color.THECOLORS['red']
185
186        self.game_object.get_shape().color = color
187        super(ShapeStateRenderer, self).render(surface)
188
189
190class Overlay(object):
191    def set_game_object(self, game_object):
192        self.game_object = game_object
193
194    def render(self, surface, display_offset, max_width):
195        pass
196
197    def is_visible(self):
198        return self.game_object.puzzler.get_state()
199
200
201class TextOverlay(Overlay):
202    def __init__(self, text, **kwargs):
203        self.text = text
204        self.widget = LabelWidget((20, 20), self.text, **kwargs)
205
206    def render(self, surface, display_offset, max_width):
207        x, y = 20, 20
208        if display_offset[0] < 0:
209            x += abs(display_offset[0])
210        if display_offset[1] < 0:
211            y += abs(display_offset[1])
212        if self.widget.rect.width > max_width - 40:
213            # Need to relayout the widget
214            factor = 2
215            while self.widget.rect.width > max_width - 40:
216                wrapped = '\n'.join(textwrap.wrap(self.text,
217                                                  len(self.text) // factor))
218                factor *= 2
219                self.widget = MultiLineWidget((20, 20), wrapped)
220                if self.widget.rect.width < 100:
221                    # safety valve
222                    break
223            self.widget.rect.topleft = (x, y)
224            self.widget.draw(surface)
225            # TODO: undo the mad folding
226        else:
227            self.widget.rect.topleft = (x, y)
228            self.widget.draw(surface)
229
230
231class TiledRenderer(Renderer):
232    """Tile the given image to fit the given outline
233
234       Outline is assumed to be in pymunk coordinates"""
235
236    def __init__(self, outline, tile_image, alpha=255):
237        self._tile_image = tile_image
238        self.outline = outline
239        self._tiled = None
240        self._offset = None
241        self._alpha = alpha
242
243    def _make_surface(self, surface, image):
244        size = surface.get_size()
245        mask = pygame.surface.Surface(size, pgl.SRCALPHA)
246        pointlist = [pymunk.pygame_util.to_pygame(p, surface)
247                     for p in self.outline]
248        rect = pygame.draw.polygon(mask,
249                                   pygame.color.Color(
250                                       255, 255, 255, self._alpha),
251                                   pointlist, 0)
252        self._offset = (rect.x, rect.y)
253        tiled = tile_surface((rect.w, rect.h), image)
254        tiled.blit(mask, (0, 0), rect,
255                   special_flags=pgl.BLEND_RGBA_MULT)
256        return tiled
257
258    def render(self, surface):
259        if not self._tiled:
260            self._tiled = self._make_surface(surface, self._tile_image)
261        surface.blit(self._tiled, self._offset)
262        super(TiledRenderer, self).render(surface)
263
264
265class TimedTiledRenderer(TiledRenderer):
266    """Animate tiles"""
267
268    # Should make this a mixin with TimeAnimate, but not right now
269
270    def __init__(self, outline, images, frame_ticks=1, alpha=255):
271        self._images = images
272        self._frame_ticks = frame_ticks
273        self.outline = outline
274        self._frames = [None] * len(images)
275        self._offset = None
276        self._alpha = alpha
277        self._frame = 0
278        self._frame_tick = 0
279
280    def advance_tick(self):
281        self._frame_tick += 1
282        if self._frame_tick > self._frame_ticks:
283            self._frame_tick = 0
284            self._frame += 1
285        if self._frame >= len(self._images):
286            self._frame = 0
287
288    def reset(self):
289        self._frame_tick = 0
290        self._frame = 0
291
292    def update(self, seconds):
293        self.advance_tick()
294
295    def render(self, surface):
296        if not self._frames[self._frame]:
297            self._frames[self._frame] = self._make_surface(
298                surface, self._images[self._frame])
299        self._tiled = self._frames[self._frame]
300        super(TimedTiledRenderer, self).render(surface)
Note: See TracBrowser for help on using the repository browser.