code:
################################################################################
## Initialization
################################################################################

## The init offset statement causes the initialization statements in this file
## to run before init statements in any other file.
init offset = -2

## Calling gui.init resets the styles to sensible default values, and sets the
## width and height of the game.
init python:
    gui.init(1920, 1080)

################################################################################
## GUI Configuration Variables
################################################################################

## Colors ######################################################################
##
## The colors of text in the interface.

## An accent color used throughout the interface to label and highlight text.
define gui.accent_color = '#66cc00'

## The color used for a text button when it is neither selected nor hovered.
define gui.idle_color = '#888888'

## The small color is used for small text, which needs to be brighter/darker to
## achieve the same effect.
define gui.idle_small_color = '#aaaaaa'

## The color that is used for buttons and bars that are hovered.
define gui.hover_color = '#a3e066'

## The color used for a text button when it is selected but not focused. A
## button is selected if it is the current screen or preference value.
define gui.selected_color = '#ffffff'

## The color used for a text button when it cannot be selected.
define gui.insensitive_color = '#8888887f'

## Colors used for the portions of bars that are not filled in. These are not
## used directly, but are used when re-generating bar image files.
define gui.muted_color = '#285100'
define gui.hover_muted_color = '#3d7a00'

## The colors used for dialogue and menu choice text.
define gui.text_color = '#ffffff'
define gui.interface_text_color = '#ffffff'

## Fonts and Font Sizes ########################################################

## The font used for in-game text.
define gui.text_font = "CRIMFBRG.ttf"

## The font used for character names.
define gui.name_text_font = "CRIMFBRG.ttf"

## The font used for out-of-game text.
define gui.interface_text_font = "CRIMFBRG.ttf"

## The size of normal dialogue text.
define gui.text_size = 33

## The size of character names.
define gui.name_text_size = 45

## The size of text in the game's user interface.
define gui.interface_text_size = 33

## The size of labels in the game's user interface.
define gui.label_text_size = 36

## The size of text on the notify screen.
define gui.notify_text_size = 24

## The size of the game's title.
define gui.title_text_size = 75

## Main and Game Menus #########################################################

## The images used for the main and game menus.
define gui.main_menu_background = "gui/main_menu.png"
define gui.game_menu_background = "gui/game_menu.png"

## Dialogue ####################################################################
##
## These variables control how dialogue is displayed on the screen one line at a
## time.

## The height of the textbox containing dialogue.
define gui.textbox_height = 278

## The placement of the textbox vertically on the screen. 0.0 is the top, 0.5 is
## center, and 1.0 is the bottom.
define gui.textbox_yalign = 1.0

## The placement of the speaking character's name, relative to the textbox.
## These can be a whole number of pixels from the left or top, or 0.5 to center.
define gui.name_xpos = 360
define gui.name_ypos = 0

## The horizontal alignment of the character's name. This can be 0.0 for left-
## aligned, 0.5 for centered, and 1.0 for right-aligned.
define gui.name_xalign = 0.0

## The width, height, and borders of the box containing the character's name, or
## None to automatically size it.
define gui.namebox_width = None
define gui.namebox_height = None

## The borders of the box containing the character's name, in left, top, right,
## bottom order.
define gui.namebox_borders = Borders(5, 5, 5, 5)

## If True, the background of the namebox will be tiled, if False, the
## background of the namebox will be scaled.
define gui.namebox_tile = False

## The placement of dialogue relative to the textbox. These can be a whole
## number of pixels relative to the left or top side of the textbox, or 0.5 to
## center.
define gui.dialogue_xpos = 402
define gui.dialogue_ypos = 75

## The maximum width of dialogue text, in pixels.
define gui.dialogue_width = 1116

## The horizontal alignment of the dialogue text. This can be 0.0 for left-
## aligned, 0.5 for centered, and 1.0 for right-aligned.
define gui.dialogue_text_xalign = 0.0

## Buttons #####################################################################
##
## These variables, along with the image files in gui/button, control aspects of
## how buttons are displayed.

## The width and height of a button, in pixels. If None, Ren'Py computes a size.
define gui.button_width = None
define gui.button_height = None

## The borders on each side of the button, in left, top, right, bottom order.
define gui.button_borders = Borders(6, 6, 6, 6)

## If True, the background image will be tiled. If False, the background image
## will be linearly scaled.
define gui.button_tile = False

## The font used by the button.
define gui.button_text_font = gui.interface_text_font

## The size of the text used by the button.
define gui.button_text_size = gui.interface_text_size

## The color of button text in various states.
define gui.button_text_idle_color = gui.idle_color
define gui.button_text_hover_color = gui.hover_color
define gui.button_text_selected_color = gui.selected_color
define gui.button_text_insensitive_color = gui.insensitive_color

## The horizontal alignment of the button text. (0.0 is left, 0.5 is center, 1.0
## is right).
define gui.button_text_xalign = 0.0

## These variables override settings for different kinds of buttons. Please see
## the gui documentation for the kinds of buttons available, and what each is
## used for.
##
## These customizations are used by the default interface:

define gui.radio_button_borders = Borders(27, 6, 6, 6)

define gui.check_button_borders = Borders(27, 6, 6, 6)

define gui.confirm_button_text_xalign = 0.5

define gui.page_button_borders = Borders(15, 6, 15, 6)

define gui.quick_button_borders = Borders(15, 6, 15, 0)
define gui.quick_button_text_size = 21
define gui.quick_button_text_idle_color = gui.idle_small_color
define gui.quick_button_text_selected_color = gui.accent_color

## You can also add your own customizations, by adding properly-named variables.
## For example, you can uncomment the following line to set the width of a
## navigation button.

# define gui.navigation_button_width = 250

## Choice Buttons ##############################################################
##
## Choice buttons are used in the in-game menus.

define gui.choice_button_width = 1185
define gui.choice_button_height = None
define gui.choice_button_tile = False
define gui.choice_button_borders = Borders(150, 8, 150, 8)
define gui.choice_button_text_font = gui.text_font
define gui.choice_button_text_size = gui.text_size
define gui.choice_button_text_xalign = 0.5
define gui.choice_button_text_idle_color = "#cccccc"
define gui.choice_button_text_hover_color = "#ffffff"
define gui.choice_button_text_insensitive_color = "#444444"

## File Slot Buttons ###########################################################
##
## A file slot button is a special kind of button. It contains a thumbnail
## image, and text describing the contents of the save slot. A save slot uses
## image files in gui/button, like the other kinds of buttons.

## The save slot button.
define gui.slot_button_width = 414
define gui.slot_button_height = 309
define gui.slot_button_borders = Borders(15, 15, 15, 15)
define gui.slot_button_text_size = 21
define gui.slot_button_text_xalign = 0.5
define gui.slot_button_text_idle_color = gui.idle_small_color
define gui.slot_button_text_selected_idle_color = gui.selected_color
define gui.slot_button_text_selected_hover_color = gui.hover_color

## The width and height of thumbnails used by the save slots.
define config.thumbnail_width = 384
define config.thumbnail_height = 216

## The number of columns and rows in the grid of save slots.
define gui.file_slot_cols = 3
define gui.file_slot_rows = 2

## Positioning and Spacing #####################################################
##
## These variables control the positioning and spacing of various user interface
## elements.

## The position of the left side of the navigation buttons, relative to the left
## side of the screen.
define gui.navigation_xpos = 60

## The vertical position of the skip indicator.
define gui.skip_ypos = 15

## The vertical position of the notify screen.
define gui.notify_ypos = 68

## The spacing between menu choices.
define gui.choice_spacing = 33

## Buttons in the navigation section of the main and game menus.
define gui.navigation_spacing = 6

## Controls the amount of spacing between preferences.
define gui.pref_spacing = 15

## Controls the amount of spacing between preference buttons.
define gui.pref_button_spacing = 0

## The spacing between file page buttons.
define gui.page_spacing = 0

## The spacing between file slots.
define gui.slot_spacing = 15

## The position of the main menu text.
define gui.main_menu_text_xalign = 1.0

## Frames ######################################################################
##
## These variables control the look of frames that can contain user interface
## components when an overlay or window is not present.

## Generic frames.
define gui.frame_borders = Borders(6, 6, 6, 6)

## The frame that is used as part of the confirm screen.
define gui.confirm_frame_borders = Borders(60, 60, 60, 60)

## The frame that is used as part of the skip screen.
define gui.skip_frame_borders = Borders(24, 8, 75, 8)

## The frame that is used as part of the notify screen.
define gui.notify_frame_borders = Borders(24, 8, 60, 8)

## Should frame backgrounds be tiled?
define gui.frame_tile = False

## Bars, Scrollbars, and Sliders ###############################################
##
## These control the look and size of bars, scrollbars, and sliders.
##
## The default GUI only uses sliders and vertical scrollbars. All of the other
## bars are only used in creator-written screens.

## The height of horizontal bars, scrollbars, and sliders. The width of vertical
## bars, scrollbars, and sliders.
define gui.bar_size = 38
define gui.scrollbar_size = 18
define gui.slider_size = 38

## True if bar images should be tiled. False if they should be linearly scaled.
define gui.bar_tile = False
define gui.scrollbar_tile = False
define gui.slider_tile = False

## Horizontal borders.
define gui.bar_borders = Borders(6, 6, 6, 6)
define gui.scrollbar_borders = Borders(6, 6, 6, 6)
define gui.slider_borders = Borders(6, 6, 6, 6)

## Vertical borders.
define gui.vbar_borders = Borders(6, 6, 6, 6)
define gui.vscrollbar_borders = Borders(6, 6, 6, 6)
define gui.vslider_borders = Borders(6, 6, 6, 6)

## What to do with unscrollable scrollbars in the gui. "hide" hides them, while
## None shows them.
define gui.unscrollable = "hide"

## History #####################################################################
##
## The history screen displays dialogue that the player has already dismissed.

## The number of blocks of dialogue history Ren'Py will keep.
define config.history_length = 250

## The height of a history screen entry, or None to make the height variable at
## the cost of performance.
define gui.history_height = 210

## The position, width, and alignment of the label giving the name of the
## speaking character.
define gui.history_name_xpos = 233
define gui.history_name_ypos = 0
define gui.history_name_width = 233
define gui.history_name_xalign = 1.0

## The position, width, and alignment of the dialogue text.
define gui.history_text_xpos = 255
define gui.history_text_ypos = 3
define gui.history_text_width = 1110
define gui.history_text_xalign = 0.0

## NVL-Mode ####################################################################
##
## The NVL-mode screen displays the dialogue spoken by NVL-mode characters.

## The borders of the background of the NVL-mode background window.
define gui.nvl_borders = Borders(0, 15, 0, 30)

## The maximum number of NVL-mode entries Ren'Py will display. When more entries
## than this are to be show, the oldest entry will be removed.
define gui.nvl_list_length = 6

## The height of an NVL-mode entry. Set this to None to have the entries
## dynamically adjust height.
define gui.nvl_height = 173

## The spacing between NVL-mode entries when gui.nvl_height is None, and between
## NVL-mode entries and an NVL-mode menu.
define gui.nvl_spacing = 15

## The position, width, and alignment of the label giving the name of the
## speaking character.
define gui.nvl_name_xpos = 645
define gui.nvl_name_ypos = 0
define gui.nvl_name_width = 225
define gui.nvl_name_xalign = 1.0

## The position, width, and alignment of the dialogue text.
define gui.nvl_text_xpos = 675
define gui.nvl_text_ypos = 12
define gui.nvl_text_width = 885
define gui.nvl_text_xalign = 0.0

## The position, width, and alignment of nvl_thought text (the text said by the
## nvl_narrator character.)
define gui.nvl_thought_xpos = 360
define gui.nvl_thought_ypos = 0
define gui.nvl_thought_width = 1170
define gui.nvl_thought_xalign = 0.0

## The position of nvl menu_buttons.
define gui.nvl_button_xpos = 675
define gui.nvl_button_xalign = 0.0

## Localization ################################################################

## This controls where a line break is permitted. The default is suitable
## for most languages. A list of available values can be found at https://
## www.renpy.org/doc/html/style_properties.html#style-property-language

define gui.language = "unicode"

################################################################################
## Mobile devices
################################################################################

init python:

    ## This increases the size of the quick buttons to make them easier to touch
    ## on tablets and phones.
    @gui.variant
    def touch():

        gui.quick_button_borders = Borders(60, 21, 60, 0)

    ## This changes the size and spacing of various GUI elements to ensure they
    ## are easily visible on phones.
    @gui.variant
    def small():

        ## Font sizes.
        gui.text_size = 45
        gui.name_text_size = 54
        gui.notify_text_size = 38
        gui.interface_text_size = 45
        gui.button_text_size = 45
        gui.label_text_size = 51

        ## Adjust the location of the textbox.
        gui.textbox_height = 360
        gui.name_xpos = 120
        gui.dialogue_xpos = 135
        gui.dialogue_width = 1650

        ## Change the size and spacing of various things.
        gui.slider_size = 54

        gui.choice_button_width = 1860
        gui.choice_button_text_size = 45

        gui.navigation_spacing = 30
        gui.pref_button_spacing = 15

        gui.history_height = 285
        gui.history_text_width = 1035

        gui.quick_button_text_size = 30

        ## File button layout.
        gui.file_slot_cols = 2
        gui.file_slot_rows = 2

        ## NVL-mode.
        gui.nvl_height = 255

        gui.nvl_name_width = 458
        gui.nvl_name_xpos = 488

        gui.nvl_text_width = 1373
        gui.nvl_text_xpos = 518
        gui.nvl_text_ypos = 8

        gui.nvl_thought_width = 1860
        gui.nvl_thought_xpos = 30

        gui.nvl_button_width = 1860
        gui.nvl_button_xpos = 30


code:
## This file contains options that can be changed to customize your game.
##
## Lines beginning with two '#' marks are comments, and you shouldn't uncomment
## them. Lines beginning with a single '#' mark are commented-out code, and you
## may want to uncomment them when appropriate.

## Basics ######################################################################

## A human-readable name of the game. This is used to set the default window
## title, and shows up in the interface and error reports.
##
## The _() surrounding the string marks it as eligible for translation.

define config.name = _("renpy-trainer")

## Determines if the title given above is shown on the main menu screen. Set
## this to False to hide the title.

define gui.show_name = True

## The version of the game.

define config.version = "0.1"

## Text that is placed on the game's about screen. Place the text between the
## triple-quotes, and leave a blank line between paragraphs.

define gui.about = _p("""
""")

## A short name for the game used for executables and directories in the built
## distribution. This must be ASCII-only, and must not contain spaces, colons,
## or semicolons.

define build.name = "renpy-trainer"

## Sounds and music ############################################################

## These three variables control, among other things, which mixers are shown
## to the player by default. Setting one of these to False will hide the
## appropriate mixer.

define config.has_sound = True
define config.has_music = True
define config.has_voice = True

## To allow the user to play a test sound on the sound or voice channel,
## uncomment a line below and use it to set a sample sound to play.

# define config.sample_sound = "sample-sound.ogg"
# define config.sample_voice = "sample-voice.ogg"

## Uncomment the following line to set an audio file that will be played while
## the player is at the main menu. This file will continue playing into the
## game, until it is stopped or another file is played.

# define config.main_menu_music = "main-menu-theme.ogg"

## Transitions #################################################################
##
## These variables set transitions that are used when certain events occur.
## Each variable should be set to a transition, or None to indicate that no
## transition should be used.

## Entering or exiting the game menu.

define config.enter_transition = dissolve
define config.exit_transition = dissolve

## Between screens of the game menu.

define config.intra_transition = dissolve

## A transition that is used after a game has been loaded.

define config.after_load_transition = None

## Used when entering the main menu after the game has ended.

define config.end_game_transition = None

## A variable to set the transition used when the game starts does not exist.
## Instead, use a with statement after showing the initial scene.

## Window management ###########################################################
##
## This controls when the dialogue window is displayed. If "show", it is always
## displayed. If "hide", it is only displayed when dialogue is present. If
## "auto", the window is hidden before scene statements and shown again once
## dialogue is displayed.
##
## After the game has started, this can be changed with the "window show",
## "window hide", and "window auto" statements.

define config.window = "hide"

## Transitions used to show and hide the dialogue window

define config.window_show_transition = Dissolve(.2)
define config.window_hide_transition = Dissolve(.2)

## Preference defaults #########################################################

## Controls the default text speed. The default, 0, is infinite, while any other
## number is the number of characters per second to type out.

default preferences.text_cps = 0

## The default auto-forward delay. Larger numbers lead to longer waits, with 0
## to 30 being the valid range.

default preferences.afm_time = 15

## Save directory ##############################################################
##
## Controls the platform-specific place Ren'Py will place the save files for
## this game. The save files will be placed in:
##
## Windows: %APPDATA\RenPy\<config.save_directory>
##
## Macintosh: $HOME/Library/RenPy/<config.save_directory>
##
## Linux: $HOME/.renpy/<config.save_directory>
##
## This generally should not be changed, and if it is, should always be a
## literal string, not an expression.

define config.save_directory = "renpy-trainer"

## Icon ########################################################################
##
## The icon displayed on the taskbar or dock.

define config.window_icon = "gui/window_icon.png"

## Build configuration #########################################################
##
## This section controls how Ren'Py turns your project into distribution files.

init python:

    ## The following functions take file patterns. File patterns are case-
    ## insensitive, and matched against the path relative to the base directory,
    ## with and without a leading /. If multiple patterns match, the first is
    ## used.
    ##
    ## In a pattern:
    ##
    ## / is the directory separator.
    ##
    ## * matches all characters, except the directory separator.
    ##
    ## ** matches all characters, including the directory separator.
    ##
    ## For example, "*.txt" matches txt files in the base directory, "game/
    ## **.ogg" matches ogg files in the game directory or any of its
    ## subdirectories, and "**.psd" matches psd files anywhere in the project.

    ## Classify files as None to exclude them from the built distributions.

    build.classify('**~', None)
    build.classify('**.bak', None)
    build.classify('**/.**', None)
    build.classify('**/#**', None)
    build.classify('**/thumbs.db', None)

    ## To archive files, classify them as 'archive'.

    # build.classify('game/**.png', 'archive')
    # build.classify('game/**.jpg', 'archive')

    ## Files matching documentation patterns are duplicated in a mac app build,
    ## so they appear in both the app and the zip file.

    build.documentation('*.html')
    build.documentation('*.txt')

## A Google Play license key is required to download expansion files and perform
## in-app purchases. It can be found on the "Services & APIs" page of the Google
## Play developer console.

# define build.google_play_key = "..."

## The username and project name associated with an itch.io project, separated
## by a slash.

# define build.itch_project = "renpytom/test-project"

repo_name: pervy/rogues

license: unknown


code:
# Copyright 2004-2015 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

# This file contains classes that handle layout of displayables on
# the screen.

from renpy.display.render import render, Render
import renpy.display
import pygame_sdl2 as pygame

def scale(num, base):
    """
    If num is a float, multiplies it by base and returns that. Otherwise,
    returns num unchanged.
    """

    if isinstance(num, float):
        return num * base
    else:
        return num

class Null(renpy.display.core.Displayable):
    """
    :doc: disp_imagelike

    A displayable that creates an empty box on the screen. The size
    of the box is controlled by `width` and `height`. This can be used
    when a displayable requires a child, but no child is suitable, or
    as a spacer inside a box.

    ::

        image logo spaced = HBox("logo.png", Null(width=100), "logo.png")

    """

    def __init__(self, width=0, height=0, **properties):
        super(Null, self).__init__(**properties)
        self.width = width
        self.height = height

    def render(self, width, height, st, at):
        rv = renpy.display.render.Render(self.width, self.height)

        if self.focusable:
            rv.add_focus(self, None, None, None, None, None)

        return rv

class Container(renpy.display.core.Displayable):
    """
    This is the base class for containers that can have one or more
    children.

    @ivar children: A list giving the children that have been added to
    this container, in the order that they were added in.

    @ivar child: The last child added to this container. This is also
    used to access the sole child in containers that can only hold
    one child.

    @ivar offsets: A list giving offsets for each of our children.
    It's expected that render will set this up each time it is called.

    @ivar sizes: A list giving sizes for each of our children. It's
    also expected that render will set this each time it is called.

    """

    # We indirect all list creation through this, so that we can
    # use RevertableLists if we want.
    _list_type = list

    def __init__(self, *args, **properties):

        self.children = self._list_type()
        self.child = None
        self.offsets = self._list_type()

        for i in args:
            self.add(i)

        super(Container, self).__init__(**properties)

    def set_style_prefix(self, prefix, root):
        super(Container, self).set_style_prefix(prefix, root)

        for i in self.children:
            i.set_style_prefix(prefix, False)

    def add(self, d):
        """
        Adds a child to this container.
        """

        child = renpy.easy.displayable(d)

        self.children.append(child)

        self.child = child
        self.offsets = self._list_type()

    def _clear(self):
        self.child = None
        self.children = self._list_type()
        self.offsets = self._list_type()

        renpy.display.render.redraw(self, 0)

    def remove(self, d):
        """
        Removes the first instance of child from this container. May
        not work with all containers.
        """

        for i, c in enumerate(self.children):
            if c is d:
                break
        else:
            return

        self.children.pop(i) # W0631
        self.offsets = self._list_type()

        if self.children:
            self.child = self.children[-1]
        else:
            self.child = None

    def update(self):
        """
        This should be called if a child is added to this
        displayable outside of the render function.
        """

        renpy.display.render.invalidate(self)

    def render(self, width, height, st, at):

        rv = Render(width, height)
        self.offsets = self._list_type()

        for c in self.children:
            cr = render(c, width, height, st, at)
            offset = c.place(rv, 0, 0, width, height, cr)
            self.offsets.append(offset)

        return rv

    def event(self, ev, x, y, st):

        children = self.children
        offsets = self.offsets

        # In #641, these went out of sync. Since they should resync on a
        # render, ignore the event for a short while rather than crashing.
        if len(offsets) != len(children):
            return None

        for i in xrange(len(offsets) - 1, -1, -1):

            d = children[i]
            xo, yo = offsets[i]

            rv = d.event(ev, x - xo, y - yo, st)
            if rv is not None:
                return rv

        return None

    def visit(self):
        return self.children

    # These interact with the ui functions to allow use as a context
    # manager.

    def __enter__(self):

        renpy.ui.context_enter(self)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):

        renpy.ui.context_exit(self)
        return False

def LiveComposite(size, *args, **properties):
    """
    :doc: disp_imagelike

    This creates a new displayable of `size`, by compositing other
    displayables. `size` is a (width, height) tuple.

    The remaining positional arguments are used to place images inside
    the LiveComposite. The remaining positional arguments should come
    in groups of two, with the first member of each group an (x, y)
    tuple, and the second member of a group is a displayable that
    is composited at that position.

    Displayables are composited from back to front.

    ::

       image eileen composite = LiveComposite(
           (300, 600),
           (0, 0), "body.png",
           (0, 0), "clothes.png",
           (50, 50), "expression.png")
    """

    properties.setdefault('style', 'image_placement')

    width, height = size

    rv = Fixed(xmaximum=width, ymaximum=height, xminimum=width, yminimum=height, **properties)

    if len(args) % 2 != 0:
        raise Exception("LiveComposite requires an odd number of arguments.")

    for pos, widget in zip(args[0::2], args[1::2]):
        xpos, ypos = pos
        rv.add(Position(widget, xpos=xpos, xanchor=0, ypos=ypos, yanchor=0))

    return rv

class Position(Container):
    """
    Controls the placement of a displayable on the screen, using
    supplied position properties. This is the non-curried form of
    Position, which should be used when the user has directly created
    the displayable that will be shown on the screen.
    """

    def __init__(self, child, style='image_placement', **properties):
        """
        @param child: The child that is being laid out.

        @param style: The base style of this position.

        @param properties: Position properties that control where the
        child of this widget is placed.
        """

        super(Position, self).__init__(style=style, **properties)
        self.add(child)

    def parameterize(self, name, parameters):

        rv = Position(self.child.parameterize('displayable', [ ]))
        rv.style = self.style.copy()

        return rv

    def render(self, width, height, st, at):

        surf = render(self.child, width, height, st, at)

        self.offsets = [ (0, 0) ]

        rv = renpy.display.render.Render(surf.width, surf.height)
        rv.blit(surf, (0, 0))

        return rv

    def get_placement(self):

        xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.child.get_placement()

        if xoffset is None:
            xoffset = 0
        if yoffset is None:
            yoffset = 0

        v = self.style.xpos
        if v is not None:
            xpos = v

        v = self.style.ypos
        if v is not None:
            ypos = v

        v = self.style.xanchor
        if v is not None:
            xanchor = v

        v = self.style.yanchor
        if v is not None:
            yanchor = v

        v = self.style.xoffset
        if v is not None:
            xoffset += v

        v = self.style.yoffset
        if v is not None:
            yoffset += v

        v = self.style.subpixel
        if (not subpixel) and (v is not None):
            subpixel = v

        return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel

class Grid(Container):
    """
    A grid is a widget that evenly allocates space to its children.
    The child widgets should not be greedy, but should instead be
    widgets that only use part of the space available to them.
    """

    def __init__(self, cols, rows, padding=None,
                 transpose=False,
                 style='grid', **properties):
        """
        @param cols: The number of columns in this widget.

        @params rows: The number of rows in this widget.

        @params transpose: True if the grid should be transposed.
        """

        if padding is not None:
            properties.setdefault('spacing', padding)

        super(Grid, self).__init__(style=style, **properties)

        cols = int(cols)
        rows = int(rows)

        self.cols = cols
        self.rows = rows

        self.transpose = transpose

    def render(self, width, height, st, at):

        # For convenience and speed.
        padding = self.style.spacing
        cols = self.cols
        rows = self.rows

        if len(self.children) != cols * rows:
            if len(self.children) < cols * rows:
                raise Exception("Grid not completely full.")
            else:
                raise Exception("Grid overfull.")

        if self.transpose:
            children = [ ]
            for y in range(rows):
                for x in range(cols):
                    children.append(self.children[y + x * rows])

        else:
            children = self.children

        # Now, start the actual rendering.

        renwidth = width
        renheight = height

        if self.style.xfill:
            renwidth = (width - (cols - 1) * padding) / cols
        if self.style.yfill:
            renheight = (height - (rows - 1) * padding) / rows

        renders = [ render(i, renwidth, renheight, st, at) for i in children ]
        sizes = [ i.get_size() for i in renders ]

        cwidth = 0
        cheight = 0

        for w, h in sizes:
            cwidth = max(cwidth, w)
            cheight = max(cheight, h)

        if self.style.xfill:
            cwidth = renwidth

        if self.style.yfill:
            cheight = renheight

        width = cwidth * cols + padding * (cols - 1)
        height = cheight * rows + padding * (rows - 1)

        rv = renpy.display.render.Render(width, height)

        offsets = [ ]

        for y in range(0, rows):
            for x in range(0, cols):

                child = children[ x + y * cols ]
                surf = renders[x + y * cols]

                xpos = x * (cwidth + padding)
                ypos = y * (cheight + padding)

                offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
                offsets.append(offset)

        if self.transpose:
            self.offsets = [ ]
            for x in range(cols):
                for y in range(rows):
                    self.offsets.append(offsets[y * cols + x])
        else:
            self.offsets = offsets

        return rv

class IgnoreLayers(Exception):
    """
    Raise this to have the event ignored by layers, but reach the
    underlay.
    """

    pass

class MultiBox(Container):

    layer_name = None
    first = True
    order_reverse = False

    def __init__(self, spacing=None, layout=None, style='default', **properties):

        if spacing is not None:
            properties['spacing'] = spacing

        super(MultiBox, self).__init__(style=style, **properties)

        self.default_layout = layout

        # The start and animation times for children of this
        # box.
        self.start_times = [ ]
        self.anim_times = [ ]

        # A map from layer name to the widget corresponding to
        # that layer.
        self.layers = None

        # The scene list for this widget.
        self.scene_list = None

    def parameterize(self, name, parameters):
        if not type(self) is MultiBox:
            return self

        if self.layers or self.scene_list:
            return self

        rv = MultiBox(layout=self.default_layout)
        rv.style = self.style.copy()

        rv.children = self._list_type(i.parameterize('displayable', [ ]) for i in self.children)
        rv.offsets = self._list_type()

        return rv

    def _clear(self):
        super(MultiBox, self)._clear()

        self.start_times = [ ]
        self.anim_times = [ ]
        self.layers = None
        self.scene_list = None

    def _in_old_scene(self):

        if self.layer_name is not None:

            if self.scene_list is None:
                return self

            scene_list = [ ]

            changed = False

            for old_sle in self.scene_list:
                new_sle = old_sle.copy()

                d = new_sle.displayable._in_old_scene()

                if d is not new_sle.displayable:
                    new_sle.displayable = d
                    changed = True

                scene_list.append(new_sle)

            if not changed:
                return self

            rv = MultiBox(layout=self.default_layout)
            rv.layer_name = self.layer_name
            rv.append_scene_list(scene_list)

        elif self.layers:
            rv = MultiBox(layout=self.default_layout)
            rv.layers = { }

            changed = False

            for layer in renpy.config.layers:
                old_d = self.layers[layer]
                new_d = old_d._in_old_scene()

                if new_d is not old_d:
                    changed = True

                rv.add(new_d)
                rv.layers[layer] = new_d

            if not changed:
                return self

        else:
            return self

        if self.offsets:
            rv.offsets = list(self.offsets)
        if self.start_times:
            rv.start_times = list(self.start_times)
        if self.anim_times:
            rv.anim_times = list(self.anim_times)

        return rv

    def __unicode__(self):
        layout = self.style.box_layout

        if layout is None:
            layout = self.default_layout

        if layout == "fixed":
            return "Fixed"
        elif layout == "horizontal":
            return "HBox"
        elif layout == "vertical":
            return "VBox"
        else:
            return "MultiBox"

    def add(self, widget, start_time=None, anim_time=None): # W0221
        super(MultiBox, self).add(widget)
        self.start_times.append(start_time)
        self.anim_times.append(anim_time)

    def append_scene_list(self, l):

        for sle in l:
            self.add(sle.displayable, sle.show_time, sle.animation_time)

        if self.scene_list is None:
            self.scene_list = [ ]

        self.scene_list.extend(l)

    def render(self, width, height, st, at):

        # Do we need to adjust the child times due to our being a layer?
        if self.layer_name or (self.layers is not None):
            adjust_times = True
        else:
            adjust_times = False

        xminimum = self.style.xminimum
        if xminimum is not None:
            width = max(width, scale(xminimum, width))

        yminimum = self.style.yminimum
        if yminimum is not None:
            height = max(height, scale(yminimum, height))

        if self.first:

            self.first = False

            if adjust_times:

                it = renpy.game.interface.interact_time

                self.start_times = [ i or it for i in self.start_times ]
                self.anim_times = [ i or it for i in self.anim_times ]

            layout = self.style.box_layout

            if layout is None:
                layout = self.default_layout

            self.layout = layout # W0201

        else:
            layout = self.layout

        # Handle time adjustment, store the results in csts and cats.
        if adjust_times:
            t = renpy.game.interface.frame_time

            csts = [ t - start for start in self.start_times ]
            cats = [ t - anim for anim in self.anim_times ]

        else:
            csts = [ st ] * len(self.children)
            cats = [ at ] * len(self.children)

        offsets = [ ]

        if layout == "fixed":

            rv = None

            if self.style.order_reverse:
                iterator = zip(reversed(self.children), reversed(csts), reversed(cats))
            else:
                iterator = zip(self.children, csts, cats)

            for child, cst, cat in iterator:

                surf = render(child, width, height, cst, cat)

                if rv is None:

                    if self.style.fit_first:
                        width, height = surf.get_size()

                    rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)

                if surf:
                    offset = child.place(rv, 0, 0, width, height, surf)
                    offsets.append(offset)
                else:
                    offsets.append((0, 0))

            if rv is None:
                rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)

            if self.style.order_reverse:
                offsets.reverse()

            self.offsets = offsets

            return rv

        # If we're here, we have a box, either horizontal or vertical. Which is good,
        # as we can share some code between boxes.

        spacing = self.style.spacing
        first_spacing = self.style.first_spacing

        if first_spacing is None:
            first_spacing = spacing

        spacings = [ first_spacing ] + [ spacing ] * (len(self.children) - 1)

        box_wrap = self.style.box_wrap

        xfill = self.style.xfill
        yfill = self.style.yfill

        # The shared height and width of the current line. The line_height must
        # be 0 for a vertical box, and the line_width must be 0 for a horizontal
        # box.
        line_width = 0
        line_height = 0

        # The children to layout.
        children = list(self.children)
        if self.style.box_reverse:
            children.reverse()
            spacings.reverse()

        # a list of (child, x, y, w, h, surf) tuples that are turned into
        # calls to child.place().
        placements = [ ]

        # The maximum x and y.
        maxx = 0
        maxy = 0

        def layout_line(line, xfill, yfill):
            """
            Lays out a single line.

            `line` a list of (child, x, y, surf) tuples.
            `xfill` the amount of space to add in the x direction.
            `yfill` the amount of space to add in the y direction.
            """

            xfill = max(0, xfill)
            yfill = max(0, yfill)

            if line:
                xperchild = xfill / len(line)
                yperchild = yfill / len(line)
            else:
                xperchild = 0
                yperchild = 0

            maxxout = maxx
            maxyout = maxy

            for i, (child, x, y, surf) in enumerate(line):
                sw, sh = surf.get_size()
                sw = max(line_width, sw)
                sh = max(line_height, sh)

                x += i * xperchild
                y += i * yperchild

                sw += xperchild
                sh += yperchild

                placements.append((child, x, y, sw, sh, surf))

                maxxout = max(maxxout, x + sw)
                maxyout = max(maxyout, y + sh)

            return maxxout, maxyout

        x = 0
        y = 0

        full_width = False
        full_height = False

        if layout == "horizontal":

            full_height = yfill

            line_height = 0
            line = [ ]
            remwidth = width

            for d, padding, cst, cat in zip(children, spacings, csts, cats):

                if box_wrap:
                    rw = width
                else:
                    rw = remwidth

                surf = render(d, rw, height - y, cst, cat)
                sw, sh = surf.get_size()

                if box_wrap and remwidth - sw - padding <= 0 and line:
                    maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)

                    y += line_height
                    x = 0
                    line_height = 0
                    remwidth = width
                    line = [ ]

                line.append((d, x, y, surf))
                line_height = max(line_height, sh)
                x += sw + padding
                remwidth -= (sw + padding)

            maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)

        elif layout == "vertical":

            full_width = xfill

            line_width = 0
            line = [ ]
            remheight = height

            for d, padding, cst, cat in zip(children, spacings, csts, cats):

                if box_wrap:
                    rh = height
                else:
                    rh = remheight

                surf = render(d, width - x, rh, cst, cat)
                sw, sh = surf.get_size()

                if box_wrap and remheight - sh - padding <= 0:
                    maxx, maxy = layout_line(line, 0, remheight if yfill else 0)

                    x += line_width
                    y = 0
                    line_width = 0
                    remheight = height
                    line = [ ]

                line.append((d, x, y, surf))
                line_width = max(line_width, sw)
                y += sh + padding
                remheight -= (sh + padding)

            maxx, maxy = layout_line(line, 0, remheight if yfill else 0)

        else:
            raise Exception("Unknown box layout: %r" % layout)

        # Back to the common for vertical and horizontal.

        if not xfill:
            width = maxx

        if not yfill:
            height = maxy

        rv = renpy.display.render.Render(width, height)

        if self.style.box_reverse ^ self.style.order_reverse:
            placements.reverse()

        for child, x, y, w, h, surf in placements:
            if full_width:
                w = width
            if full_height:
                h = height

            offset = child.place(rv, x, y, w, h, surf)
            offsets.append(offset)

        if self.style.order_reverse:
            offsets.reverse()

        self.offsets = offsets

        return rv

    def event(self, ev, x, y, st):

        children_offsets = zip(self.children, self.offsets, self.start_times)

        if not self.style.order_reverse:
            children_offsets.reverse()

        try:

            for i, (xo, yo), t in children_offsets:

                if t is None:
                    cst = st
                else:
                    cst = renpy.game.interface.event_time - t

                rv = i.event(ev, x - xo, y - yo, cst)
                if rv is not None:
                    return rv

        except IgnoreLayers:
            if self.layers:
                return None
            else:
                raise

        return None

def Fixed(**properties):
    return MultiBox(layout='fixed', **properties)

class SizeGroup(renpy.object.Object):

    def __init__(self):

        super(SizeGroup, self).__init__()

        self.members = [ ]
        self._width = None
        self.computing_width = False

    def width(self, width, height, st, at):
        if self._width is not None:
            return self._width

        if self.computing_width:
            return 0

        self.computing_width = True

        maxwidth = 0

        for i in self.members:
            rend = i.render(width, height, st, at)
            maxwidth = max(rend.width, maxwidth)

        self._width = maxwidth
        self.computing_width = False

        return maxwidth

size_groups = dict()

class Window(Container):
    """
    A window that has padding and margins, and can place a background
    behind its child. `child` is the child added to this
    displayable. All other properties are as for the :ref:`Window`
    screen language statement.
    """

    def __init__(self, child=None, style='window', **properties):
        super(Window, self).__init__(style=style, **properties)
        if child is not None:
            self.add(child)

    def visit(self):
        return [ self.style.background ] + self.children

    def get_child(self):
        return self.style.child or self.child

    def per_interact(self):
        size_group = self.style.size_group

        if size_group:
            group = size_groups.get(size_group, None)
            if group is None:
                group = size_groups[size_group] = SizeGroup()

            group.members.append(self)

    def predict_one(self):
        pd = renpy.display.predict.displayable
        self.style._predict_window(pd)

    def render(self, width, height, st, at):

        # save some typing.
        style = self.style

        xminimum = scale(style.xminimum, width)
        yminimum = scale(style.yminimum, height)

        size_group = self.style.size_group
        if size_group and size_group in size_groups:
            xminimum = max(xminimum, size_groups[size_group].width(width, height, st, at))

        left_margin = scale(style.left_margin, width)
        left_padding = scale(style.left_padding, width)

        right_margin = scale(style.right_margin, width)
        right_padding = scale(style.right_padding, width)

        top_margin = scale(style.top_margin, height)
        top_padding = scale(style.top_padding, height)

        bottom_margin = scale(style.bottom_margin, height)
        bottom_padding = scale(style.bottom_padding, height)

        # c for combined.
        cxmargin = left_margin + right_margin
        cymargin = top_margin + bottom_margin

        cxpadding = left_padding + right_padding
        cypadding = top_padding + bottom_padding

        child = self.get_child()

        # Render the child.
        surf = render(child,
                      width  - cxmargin - cxpadding,
                      height - cymargin - cypadding,
                      st, at)

        sw, sh = surf.get_size()

        # If we don't fill, shrink our size to fit.

        if not style.xfill:
            width = max(cxmargin + cxpadding + sw, xminimum)

        if not style.yfill:
            height = max(cymargin + cypadding + sh, yminimum)

        rv = renpy.display.render.Render(width, height)

        # Draw the background. The background should render at exactly the
        # requested size. (That is, be a Frame or a Solid).
        if style.background:
            bw = width - cxmargin
            bh = height - cymargin

            back = render(style.background, bw, bh, st, at)

            style.background.place(rv, left_margin, top_margin, bw, bh, back, main=False)

        offsets = child.place(rv,
                              left_margin + left_padding,
                              top_margin + top_padding,
                              width  - cxmargin - cxpadding,
                              height - cymargin - cypadding,
                              surf)

        # Draw the foreground. The background should render at exactly the
        # requested size. (That is, be a Frame or a Solid).
        if style.foreground:
            bw = width  - cxmargin
            bh = height - cymargin

            back = render(style.foreground, bw, bh, st, at)

            style.foreground.place(rv, left_margin, top_margin, bw, bh, back, main=False)

        if self.child:
            self.offsets = [ offsets ]

        self.window_size = width, height # W0201

        return rv

def dynamic_displayable_compat(st, at, expr):
    child = renpy.python.py_eval(expr)
    return child, None

class DynamicDisplayable(renpy.display.core.Displayable):
    """
    :doc: disp_dynamic

    A displayable that can change its child based on a Python
    function, over the course of an interaction.

    `function`
        A function that is called with the arguments:

        * The amount of time the displayable has been shown for.
        * The amount of time any displayable with the same tag has been shown for.
        * Any positional or keyword arguments supplied to DynamicDisplayable.

        and should return a (d, redraw) tuple, where:

        * `d` is a displayable to show.
        * `redraw` is the amount of time to wait before calling the
          function again, or None to not call the function again
          before the start of the next interaction.

        `function` is called at the start of every interaction.

    As a special case, `function` may also be a python string that evaluates
    to a displayable. In that case, function is run once per interaction.

    ::

        # If tooltip is not empty, shows it in a text. Otherwise,
        # show Null. Checks every tenth of a second to see if the
        # tooltip has been updated.
        init python:
             def show_tooltip(st, at):
                 if tooltip:
                     return tooltip, .1
                 else:
                     return Null(), .1

        image tooltipper = DynamicDisplayable(show_tooltip)

    """

    nosave = [ 'child' ]

    def after_setstate(self):
        self.child = None

    def __init__(self, function, *args, **kwargs):

        super(DynamicDisplayable, self).__init__()
        self.child = None

        if isinstance(function, basestring):
            args = ( function, )
            kwargs = { }
            function = dynamic_displayable_compat

        self.predict_function = kwargs.pop("_predict_function", None)
        self.function = function
        self.args = args
        self.kwargs = kwargs

    def visit(self):
        return [ ]

    def update(self, st, at):
        child, redraw = self.function(st, at, *self.args, **self.kwargs)
        child = renpy.easy.displayable(child)
        if redraw is None or redraw == 0:
            child.visit_all(lambda c : c.per_interact())

        self.child = child

        if redraw is not None:
            renpy.display.render.redraw(self, redraw)

#    def update(self, st, at):
#        child, redraw = self.function(st, at, *self.args, **self.kwargs)
#        child = renpy.easy.displayable(child)
#        child.visit_all(lambda c : c.per_interact())

#        self.child = child

#        if redraw is not None:
#            renpy.display.render.redraw(self, redraw)

    def per_interact(self):
        renpy.display.render.redraw(self, 0)

    def render(self, w, h, st, at):
        self.update(st, at)

        return renpy.display.render.render(self.child, w, h, st, at)

    def predict_one(self):
        try:
            if self.predict_function:
                child = self.predict_function(*self.args, **self.kwargs)
            else:
                child, _ = self.function(0, 0, *self.args, **self.kwargs)

            if child is not None:
                renpy.display.predict.displayable(child)
        except:
            pass

    def get_placement(self):
        if not self.child:
            self.update(0, 0)

        return self.child.get_placement()

    def event(self, ev, x, y, st):
        if self.child:
            return self.child.event(ev, x, y, st)

# A cache of compiled conditions used by ConditionSwitch.
cond_cache = { }

# This chooses the first member of switch that's being shown on the
# given layer.
def condition_switch_pick(switch):
    for cond, d in switch:

        if cond is None:
            return d

        if cond in cond_cache:
            code = cond_cache[cond]
        else:
            code = renpy.python.py_compile(cond, 'eval')
            cond_cache[cond] = code

        if renpy.python.py_eval_bytecode(code):
            return d

    raise Exception("Switch could not choose a displayable.")

def condition_switch_show(st, at, switch):
    return condition_switch_pick(switch), None

def condition_switch_predict(switch):

    if renpy.game.lint:
        return [ d for _cond, d in switch ]

    return [ condition_switch_pick(switch) ]

def ConditionSwitch(*args, **kwargs):
    """
    :doc: disp_dynamic

    This is a displayable that changes what it is showing based on
    python conditions. The positional argument should be given in
    groups of two, where each group consists of:

    * A string containing a python condition.
    * A displayable to use if the condition is true.

    The first true condition has its displayable shown, at least
    one condition should always be true.

    ::

        image jill = ConditionSwitch(
            "jill_beers > 4", "jill_drunk.png",
            "True", "jill_sober.png")
    """

    kwargs.setdefault('style', 'default')

    switch = [ ]

    if len(args) % 2 != 0:
        raise Exception('ConditionSwitch takes an even number of arguments')

    for cond, d in zip(args[0::2], args[1::2]):

        if cond not in cond_cache:
            code = renpy.python.py_compile(cond, 'eval')
            cond_cache[cond] = code

        d = renpy.easy.displayable(d)
        switch.append((cond, d))

    rv = DynamicDisplayable(condition_switch_show,
                            switch,
                            _predict_function=condition_switch_predict)

    return Position(rv, **kwargs)

def ShowingSwitch(*args, **kwargs):
    """
    :doc: disp_dynamic

    This is a displayable that changes what it is showing based on the
    images are showing on the screen. The positional argument should
    be given in groups of two, where each group consists of:

    * A string giving an image name, or None to indicate the default.
    * A displayable to use if the condition is true.

    A default image should be specified.

    One use of ShowingSwitch is to have side images change depending on
    the current emotion of a character. For example::

       define e = Character("Eileen",
           show_side_image=ShowingSwitch(
               "eileen happy", Image("eileen_happy_side.png", xalign=1.0, yalign=1.0),
               "eileen vhappy", Image("eileen_vhappy_side.png", xalign=1.0, yalign=1.0),
               None, Image("eileen_happy_default.png", xalign=1.0, yalign=1.0),
               )
           )
    """

    layer = kwargs.pop('layer', 'master')

    if len(args) % 2 != 0:
        raise Exception('ShowingSwitch takes an even number of positional arguments')

    condargs = [ ]

    for name, d in zip(args[0::2], args[1::2]):
        if name is not None:
            if not isinstance(name, tuple):
                name = tuple(name.split())
            cond = "renpy.showing(%r, layer=%r)" % (name, layer)
        else:
            cond = None

        condargs.append(cond)
        condargs.append(d)

    return ConditionSwitch(*condargs, **kwargs)

class IgnoresEvents(Container):

    def __init__(self, child, **properties):
        super(IgnoresEvents, self).__init__(**properties)
        self.add(child)

    def render(self, w, h, st, at):
        cr = renpy.display.render.render(self.child, w, h, st, at)
        cw, ch = cr.get_size()
        rv = renpy.display.render.Render(cw, ch)
        rv.blit(cr, (0, 0), focus=False)

        return rv

    def get_placement(self):
        return self.child.get_placement()

    # Ignores events.
    def event(self, ev, x, y, st):
        return None

def edgescroll_proportional(n):
    """
    An edgescroll function that causes the move speed to be proportional
    from the edge distance.
    """
    return n

class Viewport(Container):

    __version__ = 5

    def after_upgrade(self, version):
        if version < 1:
            self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
            self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
            self.set_adjustments = False
            self.mousewheel = False
            self.draggable = False
            self.width = 0
            self.height = 0

        if version < 2:
            self.drag_position = None

        if version < 3:
            self.edge_size = False
            self.edge_speed = False
            self.edge_function = None
            self.edge_xspeed = 0
            self.edge_yspeed = 0
            self.edge_last_st = None

        if version < 4:
            self.xadjustment_param = None
            self.yadjustment_param = None
            self.offsets_param = (None, None)
            self.set_adjustments_param = True
            self.xinitial_param = None
            self.yinitial_param = None

        if version < 5:
            self.focusable = self.draggable

    def __init__(self,
                 child=None,
                 child_size=(None, None),
                 offsets=(None, None),
                 xadjustment=None,
                 yadjustment=None,
                 set_adjustments=True,
                 mousewheel=False,
                 draggable=False,
                 edgescroll=None,
                 style='viewport',
                 xinitial=None,
                 yinitial=None,
                 replaces=None,
                 **properties):

        super(Viewport, self).__init__(style=style, **properties)
        if child is not None:
            self.add(child)

        self.xadjustment_param = xadjustment
        self.yadjustment_param = yadjustment
        self.offsets_param = offsets
        self.set_adjustments_param = set_adjustments
        self.xinitial_param = xinitial
        self.yinitial_param = yinitial

        self._show()

        if isinstance(replaces, Viewport):
            self.xadjustment.range = replaces.xadjustment.range
            self.yadjustment.range = replaces.yadjustment.range
            self.xadjustment.value = replaces.xadjustment.value
            self.yadjustment.value = replaces.yadjustment.value
            self.xoffset = replaces.xoffset
            self.yoffset = replaces.yoffset
            self.drag_position = replaces.drag_position
        else:
            self.drag_position = None

        self.child_width, self.child_height = child_size

        self.mousewheel = mousewheel
        self.draggable = draggable

        # Layout participates in the focus system so drags get migrated.
        self.focusable = draggable

        self.width = 0
        self.height = 0

        # The speed at which we scroll in the x and y directions, in pixels
        # per second.
        self.edge_xspeed = 0
        self.edge_yspeed = 0

        # The last time we edgescrolled.
        self.edge_last_st = None

        if edgescroll is not None:

            # The size of the edges that trigger scrolling.
            self.edge_size = edgescroll[0]

            # How far from the edge we can scroll.
            self.edge_speed = edgescroll[1]

            if len(edgescroll) >= 3:
                self.edge_function = edgescroll[2]
            else:
                self.edge_function = edgescroll_proportional

        else:
            self.edge_size = 0
            self.edge_speed = 0
            self.edge_function = edgescroll_proportional

    def _show(self):
        if self.xadjustment_param is None:
            self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
        else:
            self.xadjustment = self.xadjustment_param

        if self.yadjustment_param is None:
            self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
        else:
            self.yadjustment = self.yadjustment_param

        if self.xadjustment.adjustable is None:
            self.xadjustment.adjustable = True

        if self.yadjustment.adjustable is None:
            self.yadjustment.adjustable = True

        self.set_adjustments = self.set_adjustments_param

        offsets = self.offsets_param
        self.xoffset = offsets[0] if (offsets[0] is not None) else self.xinitial_param
        self.yoffset = offsets[1] if (offsets[1] is not None) else self.yinitial_param

    def per_interact(self):
        self.xadjustment.register(self)
        self.yadjustment.register(self)

    def render(self, width, height, st, at):

        self.width = width
        self.height = height

        child_width = self.child_width or width
        child_height = self.child_height or height

        surf = render(self.child, child_width, child_height, st, at)

        cw, ch = surf.get_size()

        if not self.style.xfill:
            width = min(cw, width)

        if not self.style.yfill:
            height = min(ch, height)

        width = max(width, self.style.xminimum)
        height = max(height, self.style.yminimum)

        if self.set_adjustments:
            self.xadjustment.range = max(cw - width, 0)
            self.xadjustment.page = width
            self.yadjustment.range = max(ch - height, 0)
            self.yadjustment.page = height

        if self.xoffset is not None:
            if isinstance(self.xoffset, int):
                value = self.xoffset
            else:
                value = max(cw - width, 0) * self.xoffset

            self.xadjustment.value = value

        if self.yoffset is not None:
            if isinstance(self.yoffset, int):
                value = self.yoffset
            else:
                value = max(ch - height, 0) * self.yoffset

            self.yadjustment.value = value

        if self.edge_size and (self.edge_last_st is not None) and (self.edge_xspeed or self.edge_yspeed):

            duration = max(st - self.edge_last_st, 0)
            self.xadjustment.change(self.xadjustment.value + duration * self.edge_xspeed)
            self.yadjustment.change(self.yadjustment.value + duration * self.edge_yspeed)

            self.check_edge_redraw(st)

        cxo = -int(self.xadjustment.value)
        cyo = -int(self.yadjustment.value)

        self.offsets = [ (cxo, cyo) ]

        rv = renpy.display.render.Render(width, height)
        rv.blit(surf, (cxo, cyo))

        return rv

    def check_edge_redraw(self, st):
        redraw = False

        if (self.edge_xspeed > 0) and (self.xadjustment.value < self.xadjustment.range):
            redraw = True
        if (self.edge_xspeed < 0) and (self.xadjustment.value > 0):
            redraw = True

        if (self.edge_yspeed > 0) and (self.yadjustment.value < self.yadjustment.range):
            redraw = True
        if (self.edge_yspeed < 0) and (self.yadjustment.value > 0):
            redraw = True

        if redraw:
            renpy.display.render.redraw(self, 0)
            self.edge_last_st = st
        else:
            self.edge_last_st = None

    def event(self, ev, x, y, st):

        self.xoffset = None
        self.yoffset = None

        rv = super(Viewport, self).event(ev, x, y, st)
        if rv is not None:
            return rv

        if self.draggable and renpy.display.focus.get_grab() == self:

            oldx, oldy = self.drag_position
            dx = x - oldx
            dy = y - oldy

            self.xadjustment.change(self.xadjustment.value - dx)
            self.yadjustment.change(self.yadjustment.value - dy)

            self.drag_position = (x, y) # W0201

            if renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
                renpy.display.focus.set_grab(None)
                raise renpy.display.core.IgnoreEvent()

        if not ((0 <= x < self.width) and (0 <= y <= self.height)):
            return

        if self.mousewheel:

            if renpy.display.behavior.map_event(ev, 'viewport_up'):
                rv = self.yadjustment.change(self.yadjustment.value - self.yadjustment.step)
                if rv is not None:
                    return rv
                else:
                    raise renpy.display.core.IgnoreEvent()

            if renpy.display.behavior.map_event(ev, 'viewport_down'):
                rv = self.yadjustment.change(self.yadjustment.value + self.yadjustment.step)
                if rv is not None:
                    return rv
                else:
                    raise renpy.display.core.IgnoreEvent()

        if self.draggable:

            if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
                self.drag_position = (x, y)
                renpy.display.focus.set_grab(self)
                raise renpy.display.core.IgnoreEvent()

        if self.edge_size and ev.type in [ pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP ]:

            def speed(n, zero, one):
                """
                Given a position `n`, computes the speed. The speed is 0.0
                when `n` == `zero`, 1.0 when `n` == `one`, and linearly
                interpolated when between.

                Returns 0.0 when outside the bounds - in either direction.
                """

                n = 1.0 * (n - zero) / (one - zero)
                if n < 0.0:
                    return 0.0
                if n > 1.0:
                    return 0.0

                return n

            xspeed = speed(x, self.width - self.edge_size, self.width)
            xspeed -= speed(x, self.edge_size, 0)
            self.edge_xspeed = self.edge_speed * self.edge_function(xspeed)

            yspeed = speed(y, self.height - self.edge_size, self.height)
            yspeed -= speed(y, self.edge_size, 0)
            self.edge_yspeed = self.edge_speed * self.edge_function(yspeed)

            if xspeed or yspeed:
                self.check_edge_redraw(st)
            else:
                self.edge_last_st = None

        return None

    def set_xoffset(self, offset):
        self.xoffset = offset
        renpy.display.render.redraw(self, 0)

    def set_yoffset(self, offset):
        self.yoffset = offset
        renpy.display.render.redraw(self, 0)

def LiveCrop(rect, child, **properties):
    """
    :doc: disp_imagelike

    This created a displayable by cropping `child` to `rect`, where
    `rect` is an (x, y, width, height) tuple. ::

        image eileen cropped = LiveCrop((0, 0, 300, 300), "eileen happy")
    """

    return renpy.display.motion.Transform(child, crop=rect, **properties)

class Side(Container):

    possible_positions = set([ 'tl', 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'c'])

    def after_setstate(self):
        self.sized = False

    def __init__(self, positions, style='side', **properties):

        super(Side, self).__init__(style=style, **properties)

        if isinstance(positions, basestring):
            positions = positions.split()

        seen = set()

        for i in positions:
            if not i in Side.possible_positions:
                raise Exception("Side used with impossible position '%s'." % (i,))

            if i in seen:
                raise Exception("Side used with duplicate position '%s'." % (i,))

            seen.add(i)

        self.positions = tuple(positions)
        self.sized = False

    def add(self, d):
        if len(self.children) >= len(self.positions):
            raise Exception("Side has been given too many arguments.")

        super(Side, self).add(d)

    def _clear(self):
        super(Side, self)._clear()
        self.sized = False

    def render(self, width, height, st, at):

        pos_d = { }
        pos_i = { }

        for i, (pos, d) in enumerate(zip(self.positions, self.children)):
            pos_d[pos] = d
            pos_i[pos] = i

        # Figure out the size of each widget (and hence where the
        # widget needs to be placed).

        if not self.sized:
            self.sized = True

            # Deal with various spacings.
            spacing = self.style.spacing

            def spacer(a, b, c, axis):
                if (a in pos_d) or (b in pos_d) or (c in pos_d):
                    return spacing, axis - spacing
                else:
                    return 0, axis

            self.left_space, width = spacer('tl', 'l', 'bl', width) # W0201
            self.right_space, width = spacer('tr', 'r', 'br', width) # W0201
            self.top_space, height = spacer('tl', 't', 'tr', height) # W0201
            self.bottom_space, height = spacer('bl', 'b', 'br', height) # W0201

            # The sizes of the various borders.
            left = 0
            right = 0
            top = 0
            bottom = 0
            cwidth = 0
            cheight = 0

            def sizeit(pos, width, height, owidth, oheight):
                if pos not in pos_d:
                    return owidth, oheight

                rend = render(pos_d[pos], width, height, st, at)
                rv = max(owidth, rend.width), max(oheight, rend.height)
                rend.kill()
                return rv

            cwidth, cheight = sizeit('c', width, height, 0, 0)
            cwidth, top = sizeit('t', cwidth, height, cwidth, top)
            cwidth, bottom = sizeit('b', cwidth, height, cwidth, bottom)
            left, cheight = sizeit('l', width, cheight, left, cheight)
            right, cheight = sizeit('r', width, cheight, right, cheight)

            left, top = sizeit('tl', left, top, left, top)
            left, bottom = sizeit('bl', left, bottom, left, bottom)
            right, top = sizeit('tr', right, top, right, top)
            right, bottom = sizeit('br', right, bottom, right, bottom)

            self.cwidth = cwidth # W0201
            self.cheight = cheight # W0201

            self.top = top # W0201
            self.bottom = bottom # W0201
            self.left = left # W0201
            self.right = right # W0201

        else:
            cwidth = self.cwidth
            cheight = self.cheight
            top = self.top
            bottom = self.bottom
            left = self.left
            right = self.right

        # Now, place everything onto the render.

        self.offsets = [ None ] * len(self.children)

        lefts = self.left_space
        rights = self.right_space
        tops = self.top_space
        bottoms = self.bottom_space

        cwidth = min(cwidth, width - left - lefts - right - rights)
        cheight = min(cheight, height - top - tops - bottom - bottoms)

        rv = renpy.display.render.Render(left + lefts + cwidth + rights + right,
                                         top + tops + cheight + bottoms + bottom)

        def place(pos, x, y, w, h):

            if pos not in pos_d:
                return

            d = pos_d[pos]
            i = pos_i[pos]
            rend = render(d, w, h, st, at)
            self.offsets[i] = pos_d[pos].place(rv, x, y, w, h, rend)

        col1 = 0
        col2 = left + lefts
        col3 = left + lefts + cwidth + rights

        row1 = 0
        row2 = top + tops
        row3 = top + tops + cheight + bottoms

        place('c', col2, row2, cwidth, cheight)

        place('t', col2, row1, cwidth, top)
        place('r', col3, row2, right, cheight)
        place('b', col2, row3, cwidth, bottom)
        place('l', col1, row2, left, cheight)

        place('tl', col1, row1, left, top)
        place('tr', col3, row1, right, top)
        place('br', col3, row3, right, bottom)
        place('bl', col1, row3, left, bottom)

        return rv

class Alpha(renpy.display.core.Displayable):
    def __init__(self, start, end, time, child=None, repeat=False, bounce=False,
                 anim_timebase=False, time_warp=None, **properties):

        super(Alpha, self).__init__(**properties)

        self.start = start
        self.end = end
        self.time = time
        self.child = renpy.easy.displayable(child)
        self.repeat = repeat
        self.anim_timebase = anim_timebase
        self.time_warp = time_warp

    def visit(self):
        return [ self.child ]

    def render(self, height, width, st, at):
        if self.anim_timebase:
            t = at
        else:
            t = st

        if self.time:
            done = min(t / self.time, 1.0)
        else:
            done = 1.0

        if renpy.game.less_updates:
            done = 1.0
        elif self.repeat:
            done = done % 1.0
            renpy.display.render.redraw(self, 0)
        elif done != 1.0:
            renpy.display.render.redraw(self, 0)

        if self.time_warp:
            done = self.time_warp(done)

        alpha = self.start + done * (self.end - self.start)

        rend = renpy.display.render.render(self.child, height, width, st, at)

        w, h = rend.get_size()
        rv = renpy.display.render.Render(w, h)
        rv.blit(rend, (0, 0))
        rv.alpha = alpha

        return rv

class AdjustTimes(Container):

    def __init__(self, child, start_time, anim_time, **properties):
        super(AdjustTimes, self).__init__(**properties)

        self.start_time = start_time
        self.anim_time = anim_time

        self.add(child)

    def render(self, w, h, st, at):

        if self.start_time is None:
            self.start_time = renpy.game.interface.frame_time

        if self.anim_time is None:
            self.anim_time = renpy.game.interface.frame_time

        st = renpy.game.interface.frame_time - self.start_time
        at = renpy.game.interface.frame_time - self.anim_time

        cr = renpy.display.render.render(self.child, w, h, st, at)
        cw, ch = cr.get_size()
        rv = renpy.display.render.Render(cw, ch)
        rv.blit(cr, (0, 0))

        self.offsets = [ (0, 0) ]

        return rv

    def get_placement(self):
        return self.child.get_placement()

class LiveTile(Container):
    """
    :doc: disp_imagelike

    Tiles `child` until it fills the area allocated to this displayable.

    ::

        image bg tile = LiveTile("bg.png")

    """

    def __init__(self, child, style='tile', **properties):
        super(LiveTile, self).__init__(style=style, **properties)

        self.add(child)

    def render(self, width, height, st, at):

        cr = renpy.display.render.render(self.child, width, height, st, at)
        cw, ch = cr.get_size()
        rv = renpy.display.render.Render(width, height)

        width = int(width)
        height = int(height)
        cw = int(cw)
        ch = int(ch)

        for y in range(0, height, ch):
            for x in range(0, width, cw):
                rv.blit(cr, (x, y), focus=False)

        return rv

class Flatten(Container):
    """
    :doc: disp_imagelike

    This flattens `child`, which may be made up of multiple textures, into
    a single texture.

    Certain operations, like the alpha transform property, apply to every
    texture making up a displayable, which can yield incorrect results
    when the textures overlap on screen. Flatten creates a single texture
    from multiple textures, which can prevent this problem.

    Flatten is a relatively expensive operation, and so should only be used
    when absolutely required.
    """

    def __init__(self, child, **properties):
        super(Flatten, self).__init__(**properties)

        self.add(child)

    def render(self, width, height, st, at):
        cr = renpy.display.render.render(self.child, width, height, st, at)
        cw, ch = cr.get_size()

        tex = cr.render_to_texture(True)

        rv = renpy.display.render.Render(cw, ch)
        rv.blit(tex, (0, 0))
        rv.depends_on(cr, focus=True)

        self.offsets = [ (0, 0) ]

        return rv

class AlphaMask(Container):
    """
    :doc: disp_imagelike

    This displayable takes its colors from `child`, and its alpha channel
    from the multiplication of the alpha channels of `child` and `mask`.
    The result is a displayable that has the same colors as `child`, is
    transparent where either `child` or `mask` is transparent, and is
    opaque where `child` and `mask` are both opaque.

    The `child` and `mask` parameters may be arbitrary displayables. The
    size of the AlphaMask is the size of the overlap between `child` and
    `mask`.

    Note that this takes different arguments from :func:`im.AlphaMask`,
    which uses the mask's color channel.
    """

    def __init__(self, child, mask, **properties):
        super(AlphaMask, self).__init__(**properties)

        self.add(child)
        self.mask = renpy.easy.displayable(mask)
        self.null = None
        self.size = None

    def render(self, width, height, st, at):

        cr = renpy.display.render.render(self.child, width, height, st, at)
        mr = renpy.display.render.render(self.mask, width, height, st, at)

        cw, ch = cr.get_size()
        mw, mh = mr.get_size()

        w = min(cw, mw)
        h = min(ch, mh)
        size = (w, h)

        if self.size != size:
            self.null = Null(w, h)

        nr = renpy.display.render.render(self.null, width, height, st, at)

        rv = renpy.display.render.Render(w, h, opaque=False)

        rv.operation = renpy.display.render.IMAGEDISSOLVE
        rv.operation_alpha = 1.0
        rv.operation_complete = 256.0 / (256.0 + 256.0)
        rv.operation_parameter = 256

        rv.blit(mr, (0, 0), focus=False, main=False)
        rv.blit(nr, (0, 0), focus=False, main=False)
        rv.blit(cr, (0, 0))

        return rv