diff options
Diffstat (limited to 'unrpyc/renpy/display/layout.py')
-rw-r--r-- | unrpyc/renpy/display/layout.py | 1744 |
1 files changed, 0 insertions, 1744 deletions
diff --git a/unrpyc/renpy/display/layout.py b/unrpyc/renpy/display/layout.py deleted file mode 100644 index e07e28d..0000000 --- a/unrpyc/renpy/display/layout.py +++ /dev/null @@ -1,1744 +0,0 @@ -# Copyright 2004-2013 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 - - -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 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 - - for i in range(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): - """ - :args: 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(renpy.easy.displayable(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 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() - - 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 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 necessary, transpose the grid (kinda hacky, but it works here.) - if self.transpose: - self.transpose = False - - old_children = self.children[:] - - for y in range(0, rows): - for x in range(0, cols): - self.children[x + y * cols] = old_children[ y + x * rows ] - - - # 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 self.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) - - self.offsets = [ ] - - for y in range(0, rows): - for x in range(0, cols): - - child = self.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) - self.offsets.append(offset) - - 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 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 = list(zip(reversed(self.children), reversed(csts), reversed(cats))) - else: - iterator = list(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: - sw, sh = surf.get_size() - width = min(width, sw) - height = min(height, sh) - - - 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) - - # 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 = list(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 = renpy.display.render.render(i, width, height, st, at) - maxwidth = max(rend.width, maxwidth) - renpy.display.render.invalidate(i) - - 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, 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): - # Child will be predicted by visiting. - - pd = renpy.display.predict.displayable - style = self.style - - pd(style.insensitive_background) - pd(style.idle_background) - pd(style.hover_background) - pd(style.selected_idle_background) - pd(style.selected_hover_background) - - pd(style.insensitive_child) - pd(style.idle_child) - pd(style.hover_child) - pd(style.selected_idle_child) - pd(style.selected_hover_child) - - pd(style.insensitive_foreground) - pd(style.idle_foreground) - pd(style.hover_foreground) - pd(style.selected_idle_foreground) - pd(style.selected_hover_foreground) - - 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) - - 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() - - 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, str): - args = ( function, ) - kwargs = { } - function = dynamic_displayable_compat - - self.predict_function = kwargs.pop("_predict_function", None) - self.function = function - self.args = args - self.kwargs = kwargs - self.st = 0 - self.at = 0 - - def visit(self): - return [ ] - - def per_interact(self): - child, _ = self.function(self.st, self.at, *self.args, **self.kwargs) - child = renpy.easy.displayable(child) - child.visit_all(lambda a : a.per_interact()) - - if child is not self.child: - renpy.display.render.redraw(self, 0) - self.child = child - - def render(self, w, h, st, at): - - self.st = st - self.at = 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) - - return renpy.display.render.render(self.child, w, h, st, at) - - def predict_one(self): - if not self.predict_function: - return - - for i in self.predict_function(*self.args, **self.kwargs): - if i is not None: - renpy.display.predict.displayable(i) - - def get_placement(self): - if not self.child: - self.per_interact() - - return self.child.get_placement() - - - def event(self, ev, x, y, st): - if self.child: - return self.child.event(ev, x, y, st) - -# 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 or renpy.python.py_eval(cond): - 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]): - - 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__ = 3 - - 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 - - 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) - - if xadjustment is None: - self.xadjustment = renpy.display.behavior.Adjustment(1, 0) - else: - self.xadjustment = xadjustment - - if yadjustment is None: - self.yadjustment = renpy.display.behavior.Adjustment(1, 0) - else: - self.yadjustment = yadjustment - - - 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.xoffset = offsets[0] if (offsets[0] is not None) else xinitial - self.yoffset = offsets[1] if (offsets[1] is not None) else yinitial - self.drag_position = None - - if self.xadjustment.adjustable is None: - self.xadjustment.adjustable = True - - if self.yadjustment.adjustable is None: - self.yadjustment.adjustable = True - - self.set_adjustments = set_adjustments - - self.child_width, self.child_height = child_size - - self.mousewheel = mousewheel - self.draggable = 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 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() - - # width = min(cw, width) - # height = min(ch, height) - - 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 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() - - self.edge_last_st = 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): - 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) - - - 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: - - 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() - if self.edge_last_st is None: - self.edge_last_st = 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") - """ - - x, y, w, h = rect - - return Viewport(child, offsets=(x, y), xmaximum=w, ymaximum=h, **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, str): - positions = positions.split() - - for i in positions: - if not i in Side.possible_positions: - raise Exception("Side used with impossible position '%s'." % (i,)) - - self.positions = tuple(positions) - 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 |