Ejemplo n.º 1
0
 def __init__(self, x, y, width, height, allow_oob=False,
              fx=0, fy=0, layers=None, sprites=None):
     super().__init__()
     self.camera = FlatCamera(x, y, width, height)
     self.allow_oob = allow_oob
     self.fx, self.fy = fx, fy
     if layers is None:
         self.layers = list()
     else:
         self.layers = layers
     if sprites is None:
         self.sprites = list()
     else:
         self.sprites = sprites
Ejemplo n.º 2
0
class FlatView(View):

    """Render a flat view of a scene2d.Scene.

    Attributes:

        scene           -- a scene2d.Scene instance
        camera          -- a scene2d.FlatCamera instance
        allow_oob       -- indicates whether the viewport will allow
                           viewing of out-of-bounds tile positions (ie.
                           for which there is no tile image). If set to
                           False then the map will not scroll to attempt
                           to display oob tiles.
        fx, fy          -- pixel point to center in the viewport, subject
                           to OOB checks
    """

    def __init__(self, x, y, width, height, allow_oob=False,
                 fx=0, fy=0, layers=None, sprites=None):
        super().__init__()
        self.camera = FlatCamera(x, y, width, height)
        self.allow_oob = allow_oob
        self.fx, self.fy = fx, fy
        if layers is None:
            self.layers = list()
        else:
            self.layers = layers
        if sprites is None:
            self.sprites = list()
        else:
            self.sprites = sprites

    @classmethod
    def from_window(cls, window, **kw):
        """Create a view which is the same dimensions as the supplied
        window."""
        return cls(0, 0, window.width, window.height, **kw)

    def __repr__(self):
        return '<%s object at 0x%x focus=(%d,%d) oob=%s>' % (
            self.__class__.__name__, id(self), self.fx, self.fy,
            self.allow_oob)

    #
    # EVENT HANDLING
    #
    _mouse_in_objs = set()

    def dispatch_event(self, x, y, event_type, *args):
        """ if a handler has a limit attached then use that to filter objs
        """

        # now fire the handler
        for frame in self._event_stack:
            handler = frame.get(event_type, None)
            if not handler:
                continue

            # TODO: don't do this for every handler?
            objs = list()

            # maps to pass to the handler
            if hasattr(handler, 'map_filters') and \
                    handler.map_filters is not None:
                for mf in handler.map_filters:
                    l = list()
                    for map in mf.maps:
                        cell = map.get(x, y)
                        if cell:
                            l.append(cell)
                    objs.extend((map.z, mf(l)))
            else:
                for layer in self.layers:
                    if not isinstance(layer, Map):
                        continue
                    cell = layer.get(x, y)
                    if cell:
                        objs.append((layer.z, cell))

            # sprites to pass to the handler
            if hasattr(handler, 'sprite_filters') and \
                    handler.sprite_filters is not None:
                for sf in handler.sprite_filters:
                    l = list()
                    for sprite in sf.sprites:
                        if sprite.contains(x, y):
                            l.append((sprite.z, sprite))
                    objs.extend(sf(l))
            else:
                for layer in self.layers:
                    if not isinstance(layer, SpriteLayer):
                        continue
                    for sprite in layer.sprites:
                        if sprite.contains(x, y):
                            objs.append((layer.z, sprite))
                for sprite in self.sprites:
                    if sprite.contains(x, y):
                        # un-layered sprites are at depth 0
                        objs.append((0, sprite))

            # sort by depth
            objs.sort()
            l = [obj for o, obj in objs]

            # highest to lowest
            l.reverse()

            # now gen events
            if event_type is EVENT_MOUSE_ENTER:
                active = set(l)
                l = active - self._mouse_in_objs
                if not l:
                    continue
                self._mouse_in_objs = self._mouse_in_objs | l
                l = list(l)
            elif event_type is EVENT_MOUSE_LEAVE:
                active = set(l)
                l = self._mouse_in_objs - active
                if not l:
                    continue
                self._mouse_in_objs = self._mouse_in_objs - l
                l = list(l)
            else:
                if not l:
                    continue

            ret = handler(l, *args)

            if ret != EVENT_UNHANDLED:
                break
        return None

    def on_mouse_press(self, x, y, button, modifiers):
        x, y = self.translate_position(x, y)
        self.dispatch_event(x, y, EVENT_MOUSE_PRESS, x, y, button, modifiers)
        return EVENT_UNHANDLED

    def on_mouse_release(self, x, y, button, modifiers):
        x, y = self.translate_position(x, y)
        self.dispatch_event(x, y, EVENT_MOUSE_RELEASE, x, y, button, modifiers)
        return EVENT_UNHANDLED

    def on_mouse_motion(self, x, y, dx, dy):
        x, y = self.translate_position(x, y)
        self.dispatch_event(x, y, EVENT_MOUSE_LEAVE)
        self.dispatch_event(x, y, EVENT_MOUSE_ENTER)
        return EVENT_UNHANDLED

    def on_mouse_enter(self, x, y):
        x, y = self.translate_position(x, y)
        self.dispatch_event(x, y, EVENT_MOUSE_ENTER)
        return EVENT_UNHANDLED

    def on_mouse_leave(self, x, y):
        x, y = self.translate_position(x, y)
        self.dispatch_event(x, y, EVENT_MOUSE_LEAVE)
        return EVENT_UNHANDLED

    #
    # QUERY INTERFACE
    #
    def translate_position(self, x, y):
        """Translate the on-screen pixel position to a scene pixel
        position."""
        fx, fy = self._determine_focus()
        ox, oy = self.camera.width / 2 - fx, self.camera.height / 2 - fy
        return (int(x - ox), int(y - oy))

    def get(self, x, y):
        """ Pick whatever is on the top at the position x, y. """
        r = list()

        for sprite in self.sprites:
            if sprite.contains(x, y):
                r.append(sprite)

        self.layers.sort(key=operator.attrgetter('z'))
        for layer in self.layers:
            cell = layer.get(x, y)
            if cell:
                r.append(cell)

        return r

    def tile_at(self, x, y):
        ' query for tile at given screen pixel position '
        raise NotImplemented()

    def sprite_at(self, x, y):
        ' query for sprite at given screen pixel position '
        raise NotImplemented()

    #
    # FOCUS ADJUSTMENT
    #
    def _determine_focus(self):
        """Determine the focal point of the view based on foxus (fx, fy),
        allow_oob and maps.

        Note that this method does not actually change the focus attributes
        fx and fy.
        """
        # enforce int-only positioning of focus
        fx = int(self.fx)
        fy = int(self.fy)

        if self.allow_oob:
            return (fx, fy)

        # check that any layer has bounds
        bounded = list()
        for layer in self.layers:
            if hasattr(layer, 'pxw'):
                bounded.append(layer)
        if not bounded:
            return (fx, fy)

        # figure the bounds min/max
        m = bounded[0]
        b_min_x = m.x
        b_min_y = m.y
        b_max_x = m.x + m.pxw
        b_max_y = m.y + m.pxh
        for m in bounded[1:]:
            b_min_x = min(b_min_x, m.x)
            b_min_y = min(b_min_y, m.y)
            b_max_x = min(b_max_x, m.x + m.pxw)
            b_max_y = min(b_max_y, m.y + m.pxh)

        # figure the view min/max based on focus
        w2 = self.camera.width / 2
        h2 = self.camera.height / 2

        v_min_x = fx - w2
        v_min_y = fy - h2
        x_moved = y_moved = False
        if v_min_x < b_min_x:
            fx += b_min_x - v_min_x
            x_moved = True
        if v_min_y < b_min_y:
            fy += b_min_y - v_min_y
            y_moved = True

        v_max_x = fx + w2
        v_max_y = fy + h2
        if not x_moved and v_max_x > b_max_x:
            fx -= v_max_x - b_max_x
        if not y_moved and v_max_y > b_max_y:
            fy -= v_max_y - b_max_y

        return list(map(int, (fx, fy)))

    #
    # RENDERING
    #
    def clear(self, colour=None, is_window=True):
        """Clear the view.

        If colour is None then the current glColor (is_window == False)
        or glClearColor (is_window == True) is used.

        If the view is not the whole window then you should pass
        is_window=False otherwise the whole window will be cleared.
        """
        if is_window:
            if colour is not None:
                glClearColor(*colour)
            glClear(GL_COLOR_BUFFER_BIT)
        else:
            if colour is not None:
                glColor(*colour)
            glBegin(GL_QUADS)
            glVertex2f(0, 0)
            glVertex2f(0, self.camera.height)
            glVertex2f(self.camera.width, self.camera.height)
            glVertex2f(self.camera.width, 0)
            glEnd()

    def draw(self):
        """Draw the view centered (or closest, depending on allow_oob)
        on position which is (x, y). """
        self.camera.project()

        # sort by depth
        self.layers.sort(key=operator.attrgetter('z'))

        # determine the focus point
        fx, fy = self._determine_focus()

        w2 = self.camera.width / 2
        h2 = self.camera.height / 2
        x1, y1 = fx - w2, fy - h2
        x2, y2 = fx + w2, fy + h2

        # now draw
        glPushMatrix()
        glTranslatef(
            self.camera.width / 2 - fx, self.camera.height / 2 - fy, 0)

        for layer in self.layers:
            if hasattr(layer, 'x'):
                translate = layer.x or layer.y or layer.z
            else:
                translate = False
            if translate:
                glPushMatrix()
                glTranslatef(layer.x, layer.y, layer.z)
            draw_many(layer.get_in_region(x1, y1, x2, y2))
            if translate:
                glPopMatrix()

        if self.sprites:
            draw_many(self.sprites)

        glPopMatrix()