diff options
author | Alex Xu <alex_y_xu@yahoo.ca> | 2013-08-22 22:45:26 -0400 |
---|---|---|
committer | Alex Xu <alex_y_xu@yahoo.ca> | 2013-08-22 22:45:26 -0400 |
commit | 718936110b9511631fa1f4396be992752bf8b719 (patch) | |
tree | a871768c06adc2959f8f0d69869532d36a95ffab /unrpyc/renpy/display/motion.py | |
parent | ece6cf9fbfdba9dac8d7bf98516a840c955a4853 (diff) | |
download | html5ks-718936110b9511631fa1f4396be992752bf8b719.tar.xz html5ks-718936110b9511631fa1f4396be992752bf8b719.zip |
include renpy
Diffstat (limited to 'unrpyc/renpy/display/motion.py')
-rw-r--r-- | unrpyc/renpy/display/motion.py | 1526 |
1 files changed, 1526 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/motion.py b/unrpyc/renpy/display/motion.py new file mode 100644 index 0000000..1c9d667 --- /dev/null +++ b/unrpyc/renpy/display/motion.py @@ -0,0 +1,1526 @@ +# 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 displayables that move, zoom, rotate, or otherwise +# transform displayables. (As well as displayables that support them.) +import math +import types #@UnresolvedImport + +import renpy.display #@UnusedImport +from renpy.display.render import render +from renpy.display.layout import Container + +import renpy.display.accelerator + +# The null object that's used if we don't have a defined child. +null = None + +def get_null(): + global null + + if null is None: + null = renpy.display.layout.Null() + + return null + +# Convert a position from cartesian to polar coordinates. +def cartesian_to_polar(x, y, xaround, yaround): + """ + Converts cartesian coordinates to polar coordinates. + """ + + dx = x - xaround + dy = y - yaround + + radius = math.hypot(dx, dy) + angle = math.atan2(dx, -dy) / math.pi * 180 + + if angle < 0: + angle += 360 + + return angle, radius + +def polar_to_cartesian(angle, radius, xaround, yaround): + """ + Converts polart coordinates to cartesian coordinates. + """ + + angle = angle * math.pi / 180 + + dx = radius * math.sin(angle) + dy = -radius * math.cos(angle) + + x = type(xaround)(xaround + dx) + y = type(yaround)(yaround + dy) + + return x, y + +def first_not_none(*args): + """ + Returns the first argument that is not None. + """ + + for i in args: + if i is not None: + return i + return i + + +class TransformState(renpy.object.Object): + + xoffset = None + yoffset = None + default_xpos = None + default_ypos = None + default_xanchor = None + default_yanchor = None + default_xoffset = None + default_yoffset = None + transform_anchor = False + + def __init__(self): # W0231 + self.alpha = 1 + self.rotate = None + self.rotate_pad = True + self.transform_anchor = False + self.zoom = 1 + self.xzoom = 1 + self.yzoom = 1 + + self.xpos = None + self.ypos = None + self.xanchor = None + self.yanchor = None + self.xoffset = 0 + self.yoffset = 0 + + self.xaround = 0.0 + self.yaround = 0.0 + self.xanchoraround = 0.0 + self.yanchoraround = 0.0 + + self.subpixel = False + + self.crop = None + self.corner1 = None + self.corner2 = None + self.size = None + + self.delay = 0 + + # Note: When adding a new property, we need to add it to: + # - take_state + # - diff + # - renpy.atl.PROPERTIES + # - Proxies in Transform + + # Default values for various properties, taken from our + # parent. + self.default_xpos = None + self.default_ypos = None + self.default_xanchor = None + self.default_yanchor = None + + def take_state(self, ts): + + self.alpha = ts.alpha + self.rotate = ts.rotate + self.rotate_pad = ts.rotate_pad + self.transform_anchor = ts.transform_anchor + self.zoom = ts.zoom + self.xzoom = ts.xzoom + self.yzoom = ts.yzoom + + self.xaround = ts.xaround + self.yaround = ts.yaround + self.xanchoraround = ts.xanchoraround + self.yanchoraround = ts.yanchoraround + + self.subpixel = ts.subpixel + + self.crop = ts.crop + self.corner1 = ts.corner1 + self.corner2 = ts.corner2 + self.size = ts.size + + # Take the computed position properties, not the + # raw ones. + (self.default_xpos, + self.default_ypos, + self.default_xanchor, + self.default_yanchor, + self.xoffset, + self.yoffset, + self.subpixel) = ts.get_placement() + + # Returns a dict, with p -> (old, new) where p is a property that + # has changed between this object and the new object. + def diff(self, newts): + + rv = { } + + def diff2(prop, new, old): + if new != old: + rv[prop] = (old, new) + + def diff4(prop, new, default_new, old, default_old): + if new is None: + new_value = default_new + else: + new_value = new + + if old is None: + old_value = default_old + else: + old_value = old + + if new_value != old_value: + rv[prop] = (old_value, new_value) + + diff2("alpha", newts.alpha, self.alpha) + diff2("rotate", newts.rotate, self.rotate) + diff2("rotate_pad", newts.rotate_pad, self.rotate_pad) + diff2("transform_anchor", newts.transform_anchor, self.transform_anchor) + diff2("zoom", newts.zoom, self.zoom) + diff2("xzoom", newts.xzoom, self.xzoom) + diff2("yzoom", newts.yzoom, self.yzoom) + + diff2("xaround", newts.xaround, self.xaround) + diff2("yaround", newts.yaround, self.yaround) + diff2("xanchoraround", newts.xanchoraround, self.xanchoraround) + diff2("yanchoraround", newts.yanchoraround, self.yanchoraround) + + diff2("subpixel", newts.subpixel, self.subpixel) + + diff2("crop", newts.crop, self.crop) + diff2("corner1", newts.corner1, self.corner1) + diff2("corner2", newts.corner2, self.corner2) + diff2("size", newts.size, self.size) + + diff4("xpos", newts.xpos, newts.default_xpos, self.xpos, self.default_xpos) + + diff4("xanchor", newts.xanchor, newts.default_xanchor, self.xanchor, self.default_xanchor) + diff2("xoffset", newts.xoffset, self.xoffset) + + diff4("ypos", newts.ypos, newts.default_ypos, self.ypos, self.default_ypos) + diff4("yanchor", newts.yanchor, newts.default_yanchor, self.yanchor, self.default_yanchor) + diff2("yoffset", newts.yoffset, self.yoffset) + + return rv + + def get_placement(self, cxoffset=0, cyoffset=0): + + return ( + first_not_none(self.xpos, self.default_xpos), + first_not_none(self.ypos, self.default_ypos), + first_not_none(self.xanchor, self.default_xanchor), + first_not_none(self.yanchor, self.default_yanchor), + self.xoffset + cxoffset, + self.yoffset + cyoffset, + self.subpixel, + ) + + # These update various properties. + def get_xalign(self): + return self.xpos + + def set_xalign(self, v): + self.xpos = v + self.xanchor = v + + xalign = property(get_xalign, set_xalign) + + def get_yalign(self): + return self.ypos + + def set_yalign(self, v): + self.ypos = v + self.yanchor = v + + yalign = property(get_yalign, set_yalign) + + def get_around(self): + return (self.xaround, self.yaround) + + def set_around(self, value): + self.xaround, self.yaround = value + self.xanchoraround, self.yanchoraround = None, None + + def set_alignaround(self, value): + self.xaround, self.yaround = value + self.xanchoraround, self.yanchoraround = value + + around = property(get_around, set_around) + alignaround = property(get_around, set_alignaround) + + def get_angle(self): + xpos = first_not_none(self.xpos, self.default_xpos, 0) + ypos = first_not_none(self.ypos, self.default_ypos, 0) + angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround) + return angle + + def get_radius(self): + xpos = first_not_none(self.xpos, self.default_xpos, 0) + ypos = first_not_none(self.ypos, self.default_ypos, 0) + _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround) + return radius + + def set_angle(self, value): + xpos = first_not_none(self.xpos, self.default_xpos, 0) + ypos = first_not_none(self.ypos, self.default_ypos, 0) + _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround) + angle = value + self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround) + + if self.xanchoraround: + self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround) + + def set_radius(self, value): + xpos = first_not_none(self.xpos, self.default_xpos, 0) + ypos = first_not_none(self.ypos, self.default_ypos, 0) + angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround) + radius = value + self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround) + + if self.xanchoraround: + self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround) + + angle = property(get_angle, set_angle) + radius = property(get_radius, set_radius) + + def get_pos(self): + return self.xpos, self.ypos + + def set_pos(self, value): + self.xpos, self.ypos = value + + pos = property(get_pos, set_pos) + + def get_anchor(self): + return self.xanchor, self.yanchor + + def set_anchor(self, value): + self.xanchor, self.yanchor = value + + anchor = property(get_anchor, set_anchor) + + def get_align(self): + return self.xpos, self.ypos + + def set_align(self, value): + self.xanchor, self.yanchor = value + self.xpos, self.ypos = value + + align = property(get_align, set_align) + + def get_offset(self): + return self.xoffset, self.yoffset + + def set_offset(self, value): + self.xoffset, self.yoffset = value + + offset = property(get_offset, set_offset) + + def set_xcenter(self, value): + self.xpos = value + self.xanchor = 0.5 + + def get_xcenter(self): + return self.xpos + + def set_ycenter(self, value): + self.ypos = value + self.yanchor = 0.5 + + def get_ycenter(self): + return self.ypos + + xcenter = property(get_xcenter, set_xcenter) + ycenter = property(get_ycenter, set_ycenter) + +class Proxy(object): + """ + This class proxies a field from the transform to its state. + """ + + def __init__(self, name): + self.name = name + + def __get__(self, instance, owner): + return getattr(instance.state, self.name) + + def __set__(self, instance, value): + return setattr(instance.state, self.name, value) + +class Transform(Container): + """ + Documented in sphinx, because we can't scan this object. + """ + + __version__ = 5 + transform_event_responder = True + + # Proxying things over to our state. + alpha = Proxy("alpha") + rotate = Proxy("rotate") + rotate_pad = Proxy("rotate_pad") + transform_anchor = Proxy("rotate_pad") + zoom = Proxy("zoom") + xzoom = Proxy("xzoom") + yzoom = Proxy("yzoom") + + xpos = Proxy("xpos") + ypos = Proxy("ypos") + xanchor = Proxy("xanchor") + yanchor = Proxy("yanchor") + + xalign = Proxy("xalign") + yalign = Proxy("yalign") + + around = Proxy("around") + alignaround = Proxy("alignaround") + angle = Proxy("angle") + radius = Proxy("radius") + + xaround = Proxy("xaround") + yaround = Proxy("yaround") + xanchoraround = Proxy("xanchoraround") + yanchoraround = Proxy("yanchoraround") + + pos = Proxy("pos") + anchor = Proxy("anchor") + align = Proxy("align") + + crop = Proxy("crop") + corner1 = Proxy("corner1") + corner2 = Proxy("corner2") + size = Proxy("size") + + delay = Proxy("delay") + + xoffset = Proxy("xoffset") + yoffset = Proxy("yoffset") + offset = Proxy("offset") + + subpixel = Proxy("subpixel") + + xcenter = Proxy("xcenter") + ycenter = Proxy("ycenter") + + def after_upgrade(self, version): + + if version < 1: + self.active = False + self.state = TransformState() + + self.state.xpos = self.xpos or 0 + self.state.ypos = self.ypos or 0 + self.state.xanchor = self.xanchor or 0 + self.state.yanchor = self.yanchor or 0 + self.state.alpha = self.alpha + self.state.rotate = self.rotate + self.state.zoom = self.zoom + self.state.xzoom = self.xzoom + self.state.yzoom = self.yzoom + + self.hide_request = False + self.hide_response = True + + if version < 2: + self.st = 0 + self.at = 0 + + if version < 3: + self.st_offset = 0 + self.at_offset = 0 + self.child_st_base = 0 + + if version < 4: + self.style_arg = 'transform' + + if version < 5: + self.replaced_request = False + self.replaced_response = True + + DEFAULT_ARGUMENTS = { + "selected_activate" : { }, + "selected_hover" : { }, + "selected_idle" : { }, + "selected_insensitive" : { }, + "activate" : { }, + "hover" : { }, + "idle" : { }, + "insensitive" : { }, + "" : { }, + } + + # Compatibility with old versions of the class. + active = False + children = False + arguments = DEFAULT_ARGUMENTS + + def __init__(self, + child=None, + function=None, + + style='transform', + focus=None, + default=False, + + **kwargs): + + self.kwargs = kwargs + self.style_arg = style + + super(Transform, self).__init__(style=style, focus=focus, default=default) + + self.function = function + + child = renpy.easy.displayable_or_none(child) + if child is not None: + self.add(child) + + self.state = TransformState() + + self.arguments = dict((k, {}) for k in self.DEFAULT_ARGUMENTS) + + # Split up the keyword arguments. + for k, v in kwargs.items(): + if "_" in k: + prefix, prop = k.rsplit("_", 1) + else: + prefix = "" + prop = k + + if prefix not in self.arguments: + raise Exception("Unknown transform property prefix: %r" % prefix) + + if prop not in renpy.atl.PROPERTIES: + raise Exception("Unknown transform property: %r") + + self.arguments[prefix][prop] = v + + + # Apply the keyword arguments. + for k, v in kwargs.items(): + setattr(self.state, k, v) + + # This is the matrix transforming our coordinates into child coordinates. + self.forward = None + + # Have we called the function at least once? + self.active = False + + # Have we been requested to hide? + self.hide_request = False + + # True if it's okay for us to hide. + self.hide_response = True + + # Have we been requested to replaced? + self.replaced_request = False + + # True if it's okay for us to replaced. + self.replaced_response = True + + self.st = 0 + self.at = 0 + self.st_offset = 0 + self.at_offset = 0 + + self.child_st_base = 0 + + def visit(self): + if self.child is None: + return [ ] + else: + return [ self.child ] + + # The default function chooses entries from self.arguments that match + # the style prefix, and applies them to the state. + def default_function(self, state, st, at): + + prefix = self.style.prefix.strip("_") + prefixes = [ ] + + while prefix: + prefixes.insert(0, prefix) + _, _, prefix = prefix.partition("_") + + prefixes.insert(0, "") + + for i in prefixes: + for k, v in self.arguments[i].items(): + setattr(state, k, v) + + return None + + def set_transform_event(self, event): + if self.child is not None: + self.child.set_transform_event(event) + + super(Transform, self).set_transform_event(event) + + + def take_state(self, t): + """ + Takes the transformation state from object t into this object. + """ + + self.state.take_state(t.state) + + # The arguments will be applied when the default function is + # called. + + + def take_execution_state(self, t): + """ + Takes the execution state from object t into this object. This is + overridden by renpy.atl.TransformBase. + """ + + self.hide_request = t.hide_request + self.replaced_request = t.replaced_request + + self.state.xpos = t.state.xpos + self.state.ypos = t.state.ypos + self.state.xanchor = t.state.xanchor + self.state.yanchor = t.state.yanchor + + if isinstance(self.child, Transform) and isinstance(t.child, Transform): + self.child.take_execution_state(t.child) + + + def copy(self): + """ + Makes a copy of this transform. + """ + + d = self() + d.kwargs = { } + d.take_state(self) + d.take_execution_state(self) + d.st = self.st + d.at = self.at + + return d + + def _change_transform_child(self, child): + rv = self.copy() + + if self.child is not None: + rv.set_child(self.child._change_transform_child(child)) + + return rv + + def _hide(self, st, at, kind): + + if not self.child: + return None + + if not (self.hide_request or self.replaced_request): + d = self.copy() + else: + d = self + + d.st_offset = self.st_offset + d.at_offset = self.at_offset + + if kind == "hide": + d.hide_request = True + else: + d.replaced_request = True + + d.hide_response = True + d.replaced_response = True + + if d.function is not None: + d.function(d, st + d.st_offset, at + d.at_offset) + + new_child = d.child._hide(st, at, kind) + + if new_child is not None: + d.child = new_child + d.hide_response = False + d.replaced_response = False + + if (not d.hide_response) or (not d.replaced_response): + renpy.display.render.redraw(d, 0) + return d + + return None + + def set_child(self, child): + + child = renpy.easy.displayable(child) + + self.child = child + self.child_st_base = self.st + + child.per_interact() + + renpy.display.render.redraw(self, 0) + + def update_state(self): + """ + This updates the state to that at self.st, self.at. + """ + + # If we have to, call the function that updates this transform. + if self.function is not None: + fr = self.function(self, self.st, self.at) + else: + fr = self.default_function(self, self.st, self.at) + + # Order a redraw, if necessary. + if fr is not None: + renpy.display.render.redraw(self, fr) + + state = self.state + + self.active = True + + # Use non-None elements of the child placement as defaults. + child = self.child + if child is not None and renpy.config.transform_uses_child_position: + + pos = child.get_placement() + + if pos[0] is not None: + state.default_xpos = pos[0] + if pos[2] is not None: + state.default_xanchor = pos[2] + if pos[1] is not None: + state.default_ypos = pos[1] + if pos[3] is not None: + state.default_yanchor = pos[3] + + state.subpixel |= pos[6] + + # The render method is now defined in accelerator.pyx. + + def event(self, ev, x, y, st): + + if self.hide_request: + return None + + children = self.children + offsets = self.offsets + + if not offsets: + return None + + for i in range(len(self.children)-1, -1, -1): + + d = children[i] + xo, yo = offsets[i] + + cx = x - xo + cy = y - yo + + # Transform screen coordinates to child coordinates. + cx, cy = self.forward.transform(cx, cy) + + rv = d.event(ev, cx, cy, st) + if rv is not None: + return rv + + return None + + def __call__(self, child=None, take_state=True): + + if child is None: + child = self.child + + # If we don't have a child for some reason, set it to null. + if child is None: + child = get_null() + + rv = Transform( + child=child, + function=self.function, + style=self.style_arg, + **self.kwargs) + + rv.take_state(self) + + return rv + + def get_placement(self): + + if not self.active: + self.update_state() + + if self.child is not None: + _cxpos, _cypos, _cxanchor, _cyanchor, cxoffset, cyoffset, _csubpixel = self.child.get_placement() + else: + cxoffset = 0 + cyoffset = 0 + + cxoffset = cxoffset or 0 + cyoffset = cyoffset or 0 + + rv = self.state.get_placement(cxoffset, cyoffset) + + if self.state.transform_anchor: + + xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = rv + if (xanchor is not None) and (yanchor is not None): + + cw, ch = self.child_size + rw, rh = self.render_size + + if isinstance(xanchor, float): + xanchor *= cw + if isinstance(yanchor, float): + yanchor *= ch + + xanchor -= cw / 2.0 + yanchor -= ch / 2.0 + + xanchor, yanchor = self.reverse.transform(xanchor, yanchor) + + xanchor += rw / 2.0 + yanchor += rh / 2.0 + + xanchor = renpy.display.core.absolute(xanchor) + yanchor = renpy.display.core.absolute(yanchor) + + rv = (xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel) + + return rv + + def update(self): + """ + This should be called when a transform property field is updated outside + of the callback method, to ensure that the change takes effect. + """ + + renpy.display.render.invalidate(self) + + def parameterize(self, name, parameters): + if parameters: + raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" % + (' '.join(name), ' '.join(parameters))) + + # Note the call here. + return self() + + def _show(self): + self.update_state() + +Transform.render = types.MethodType(renpy.display.accelerator.transform_render, None, Transform) + +class ATLTransform(renpy.atl.ATLTransformBase, Transform): + + def __init__(self, atl, child=None, context={}, parameters=None, **properties): + renpy.atl.ATLTransformBase.__init__(self, atl, context, parameters) + Transform.__init__(self, child=child, function=self.execute, **properties) + + self.raw_child = self.child + + def _show(self): + super(ATLTransform, self)._show() + self.execute(self, self.st, self.at) + + +class Motion(Container): + """ + This is used to move a child displayable around the screen. It + works by supplying a time value to a user-supplied function, + which is in turn expected to return a pair giving the x and y + location of the upper-left-hand corner of the child, or a + 4-tuple giving that and the xanchor and yanchor of the child. + + The time value is a floating point number that ranges from 0 to + 1. If repeat is True, then the motion repeats every period + sections. (Otherwise, it stops.) If bounce is true, the + time value varies from 0 to 1 to 0 again. + + The function supplied needs to be pickleable, which means it needs + to be defined as a name in an init block. It cannot be a lambda or + anonymous inner function. If you can get away with using Pan or + Move, use them instead. + + Please note that floats and ints are interpreted as for xpos and + ypos, with floats being considered fractions of the screen. + """ + + def __init__(self, function, period, child=None, new_widget=None, old_widget=None, repeat=False, bounce=False, delay=None, anim_timebase=False, tag_start=None, time_warp=None, add_sizes=False, style='motion', **properties): + """ + @param child: The child displayable. + + @param new_widget: If child is None, it is set to new_widget, + so that we can speak the transition protocol. + + @param old_widget: Ignored, for compatibility with the transition protocol. + + @param function: A function that takes a floating point value and returns + an xpos, ypos tuple. + + @param period: The amount of time it takes to go through one cycle, in seconds. + + @param repeat: Should we repeat after a period is up? + + @param bounce: Should we bounce? + + @param delay: How long this motion should take. If repeat is None, defaults to period. + + @param anim_timebase: If True, use the animation timebase rather than the shown timebase. + + @param time_warp: If not None, this is a function that takes a + fraction of the period (between 0.0 and 1.0), and returns a + new fraction of the period. Use this to warp time, applying + acceleration and deceleration to motions. + + This can also be used as a transition. When used as a + transition, the motion is applied to the new_widget for delay + seconds. + """ + + if child is None: + child = new_widget + + if delay is None and not repeat: + delay = period + + super(Motion, self).__init__(style=style, **properties) + + if child is not None: + self.add(child) + + self.function = function + self.period = period + self.repeat = repeat + self.bounce = bounce + self.delay = delay + self.anim_timebase = anim_timebase + self.time_warp = time_warp + self.add_sizes = add_sizes + + self.position = None + + + def get_placement(self): + + if self.position is None: + return super(Motion, self).get_placement() + else: + return self.position + (self.style.xoffset, self.style.yoffset, self.style.subpixel) + + def render(self, width, height, st, at): + + if self.anim_timebase: + t = at + else: + t = st + + if renpy.game.less_updates: + if self.delay: + t = self.delay + if self.repeat: + t = t % self.period + else: + t = self.period + elif self.delay and t >= self.delay: + t = self.delay + if self.repeat: + t = t % self.period + elif self.repeat: + t = t % self.period + renpy.display.render.redraw(self, 0) + else: + if t > self.period: + t = self.period + else: + renpy.display.render.redraw(self, 0) + + if self.period > 0: + t /= self.period + else: + t = 1 + + if self.time_warp: + t = self.time_warp(t) + + if self.bounce: + t = t * 2 + if t > 1.0: + t = 2.0 - t + + child = render(self.child, width, height, st, at) + cw, ch = child.get_size() + + if self.add_sizes: + res = self.function(t, (width, height, cw, ch)) + else: + res = self.function(t) + + res = tuple(res) + + if len(res) == 2: + self.position = res + (self.style.xanchor, self.style.yanchor) + else: + self.position = res + + rv = renpy.display.render.Render(cw, ch) + rv.blit(child, (0, 0)) + + self.offsets = [ (0, 0) ] + + return rv + + +class Interpolate(object): + + anchors = { + 'top' : 0.0, + 'center' : 0.5, + 'bottom' : 1.0, + 'left' : 0.0, + 'right' : 1.0, + } + + def __init__(self, start, end): + + if len(start) != len(end): + raise Exception("The start and end must have the same number of arguments.") + + self.start = [ self.anchors.get(i, i) for i in start ] + self.end = [ self.anchors.get(i, i) for i in end ] + + def __call__(self, t, sizes=(None, None, None, None)): + + def interp(a, b, c): + + if c is not None: + if type(a) is float: + a = a * c + if type(b) is float: + b = b * c + + rv = a + t * (b - a) + + return renpy.display.core.absolute(rv) + + return [ interp(a, b, c) for a, b, c in zip(self.start, self.end, sizes) ] + + +def Pan(startpos, endpos, time, child=None, repeat=False, bounce=False, + anim_timebase=False, style='motion', time_warp=None, **properties): + """ + This is used to pan over a child displayable, which is almost + always an image. It works by interpolating the placement of the + upper-left corner of the screen, over time. It's only really + suitable for use with images that are larger than the screen, + and we don't do any cropping on the image. + + @param startpos: The initial coordinates of the upper-left + corner of the screen, relative to the image. + + @param endpos: The coordinates of the upper-left corner of the + screen, relative to the image, after time has elapsed. + + @param time: The time it takes to pan from startpos to endpos. + + @param child: The child displayable. + + @param repeat: True if we should repeat this forever. + + @param bounce: True if we should bounce from the start to the end + to the start. + + @param anim_timebase: True if we use the animation timebase, False to use the + displayable timebase. + + @param time_warp: If not None, this is a function that takes a + fraction of the period (between 0.0 and 1.0), and returns a + new fraction of the period. Use this to warp time, applying + acceleration and deceleration to motions. + + This can be used as a transition. See Motion for details. + """ + + x0, y0 = startpos + x1, y1 = endpos + + return Motion(Interpolate((-x0, -y0), (-x1, -y1)), + time, + child, + repeat=repeat, + bounce=bounce, + style=style, + anim_timebase=anim_timebase, + time_warp=time_warp, + add_sizes=True, + **properties) + +def Move(startpos, endpos, time, child=None, repeat=False, bounce=False, + anim_timebase=False, style='motion', time_warp=None, **properties): + """ + This is used to pan over a child displayable relative to + the containing area. It works by interpolating the placement of the + the child, over time. + + @param startpos: The initial coordinates of the child + relative to the containing area. + + @param endpos: The coordinates of the child at the end of the + move. + + @param time: The time it takes to move from startpos to endpos. + + @param child: The child displayable. + + @param repeat: True if we should repeat this forever. + + @param bounce: True if we should bounce from the start to the end + to the start. + + @param anim_timebase: True if we use the animation timebase, False to use the + displayable timebase. + + @param time_warp: If not None, this is a function that takes a + fraction of the period (between 0.0 and 1.0), and returns a + new fraction of the period. Use this to warp time, applying + acceleration and deceleration to motions. + + This can be used as a transition. See Motion for details. + """ + + return Motion(Interpolate(startpos, endpos), + time, + child, + repeat=repeat, + bounce=bounce, + anim_timebase=anim_timebase, + style=style, + time_warp=time_warp, + add_sizes=True, + **properties) + + +class Revolver(object): + + def __init__(self, start, end, child, around=(0.5, 0.5), cor=(0.5, 0.5), pos=None): + self.start = start + self.end = end + self.around = around + self.cor = cor + self.pos = pos + self.child = child + + def __call__(self, t, xxx_todo_changeme): + + # Converts a float to an integer in the given range, passes + # integers through unchanged. + (w, h, cw, ch) = xxx_todo_changeme + def fti(x, r): + if x is None: + x = 0 + + if isinstance(x, float): + return int(x * r) + else: + return x + + if self.pos is None: + pos = self.child.get_placement() + else: + pos = self.pos + + xpos, ypos, xanchor, yanchor, _xoffset, _yoffset, _subpixel = pos + + xpos = fti(xpos, w) + ypos = fti(ypos, h) + xanchor = fti(xanchor, cw) + yanchor = fti(yanchor, ch) + + xaround, yaround = self.around + + xaround = fti(xaround, w) + yaround = fti(yaround, h) + + xcor, ycor = self.cor + + xcor = fti(xcor, cw) + ycor = fti(ycor, ch) + + angle = self.start + (self.end - self.start) * t + angle *= math.pi / 180 + + # The center of rotation, relative to the xaround. + x = xpos - xanchor + xcor - xaround + y = ypos - yanchor + ycor - yaround + + # Rotate it. + nx = x * math.cos(angle) - y * math.sin(angle) + ny = x * math.sin(angle) + y * math.cos(angle) + + # Project it back. + nx = nx - xcor + xaround + ny = ny - ycor + yaround + + return (renpy.display.core.absolute(nx), renpy.display.core.absolute(ny), 0, 0) + + +def Revolve(start, end, time, child, around=(0.5, 0.5), cor=(0.5, 0.5), pos=None, **properties): + + return Motion(Revolver(start, end, child, around=around, cor=cor, pos=pos), + time, + child, + add_sizes=True, + **properties) + + + +def zoom_render(crend, x, y, w, h, zw, zh, bilinear): + """ + This creates a render that zooms its child. + + `crend` - The render of the child. + `x`, `y`, `w`, `h` - A rectangle inside the child. + `zw`, `zh` - The size the rectangle is rendered to. + `bilinear` - Should we be rendering in bilinear mode? + """ + + rv = renpy.display.render.Render(zw, zh) + + if zw == 0 or zh == 0 or w == 0 or h == 0: + return rv + + + rv.forward = renpy.display.render.Matrix2D(w / zw, 0, 0, h / zh) + rv.reverse = renpy.display.render.Matrix2D(zw / w, 0, 0, zh / h) + + rv.clipping = True + + rv.blit(crend, rv.reverse.transform(-x, -y)) + + return rv + + +class ZoomCommon(renpy.display.core.Displayable): + def __init__(self, + time, child, + end_identity=False, + after_child=None, + time_warp=None, + bilinear=True, + opaque=True, + anim_timebase=False, + repeat=False, + style='motion', + **properties): + """ + @param time: The amount of time it will take to + interpolate from the start to the end rectange. + + @param child: The child displayable. + + @param after_child: If present, a second child + widget. This displayable will be rendered after the zoom + completes. Use this to snap to a sharp displayable after + the zoom is done. + + @param time_warp: If not None, this is a function that takes a + fraction of the period (between 0.0 and 1.0), and returns a + new fraction of the period. Use this to warp time, applying + acceleration and deceleration to motions. + """ + + super(ZoomCommon, self).__init__(style=style, **properties) + + child = renpy.easy.displayable(child) + + self.time = time + self.child = child + self.repeat = repeat + + if after_child: + self.after_child = renpy.easy.displayable(after_child) + else: + if end_identity: + self.after_child = child + else: + self.after_child = None + + self.time_warp = time_warp + self.bilinear = bilinear + self.opaque = opaque + self.anim_timebase = anim_timebase + + + def visit(self): + return [ self.child, self.after_child ] + + def render(self, width, height, 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 self.repeat: + done = done % 1.0 + + if renpy.game.less_updates: + done = 1.0 + + self.done = done + + if self.after_child and done == 1.0: + return renpy.display.render.render(self.after_child, width, height, st, at) + + if self.time_warp: + done = self.time_warp(done) + + rend = renpy.display.render.render(self.child, width, height, st, at) + + rx, ry, rw, rh, zw, zh = self.zoom_rectangle(done, rend.width, rend.height) + + if rx < 0 or ry < 0 or rx + rw > rend.width or ry + rh > rend.height: + raise Exception("Zoom rectangle %r falls outside of %dx%d parent surface." % ((rx, ry, rw, rh), rend.width, rend.height)) + + rv = zoom_render(rend, rx, ry, rw, rh, zw, zh, self.bilinear) + + if self.done < 1.0: + renpy.display.render.redraw(self, 0) + + return rv + + def event(self, ev, x, y, st): + + if not self.time: + done = 1.0 + else: + done = min(st / self.time, 1.0) + + if done == 1.0 and self.after_child: + return self.after_child.event(ev, x, y, st) + else: + return None + + +class Zoom(ZoomCommon): + + def __init__(self, size, start, end, time, child, **properties): + + end_identity = (end == (0.0, 0.0) + size) + + super(Zoom, self).__init__(time, child, end_identity=end_identity, **properties) + + self.size = size + self.start = start + self.end = end + + def zoom_rectangle(self, done, width, height): + + rx, ry, rw, rh = [ (a + (b - a) * done) for a, b in zip(self.start, self.end) ] + + return rx, ry, rw, rh, self.size[0], self.size[1] + + +class FactorZoom(ZoomCommon): + + def __init__(self, start, end, time, child, **properties): + + end_identity = (end == 1.0) + + super(FactorZoom, self).__init__(time, child, end_identity=end_identity, **properties) + + self.start = start + self.end = end + + def zoom_rectangle(self, done, width, height): + + factor = self.start + (self.end - self.start) * done + + return 0, 0, width, height, factor * width, factor * height + + + +class SizeZoom(ZoomCommon): + + def __init__(self, start, end, time, child, **properties): + + end_identity = False + + super(SizeZoom, self).__init__(time, child, end_identity=end_identity, **properties) + + self.start = start + self.end = end + + def zoom_rectangle(self, done, width, height): + + sw, sh = self.start + ew, eh = self.end + + zw = sw + (ew - sw) * done + zh = sh + (eh - sh) * done + + return 0, 0, width, height, zw, zh + + +class RotoZoom(renpy.display.core.Displayable): + + transform = None + + def __init__(self, + rot_start, + rot_end, + rot_delay, + zoom_start, + zoom_end, + zoom_delay, + child, + rot_repeat=False, + zoom_repeat=False, + rot_bounce=False, + zoom_bounce=False, + rot_anim_timebase=False, + zoom_anim_timebase=False, + rot_time_warp=None, + zoom_time_warp=None, + opaque=False, + style='motion', + **properties): + + super(RotoZoom, self).__init__(style=style, **properties) + + self.rot_start = rot_start + self.rot_end = rot_end + self.rot_delay = rot_delay + + self.zoom_start = zoom_start + self.zoom_end = zoom_end + self.zoom_delay = zoom_delay + + self.child = renpy.easy.displayable(child) + + self.rot_repeat = rot_repeat + self.zoom_repeat = zoom_repeat + + self.rot_bounce = rot_bounce + self.zoom_bounce = zoom_bounce + + self.rot_anim_timebase = rot_anim_timebase + self.zoom_anim_timebase = zoom_anim_timebase + + self.rot_time_warp = rot_time_warp + self.zoom_time_warp = zoom_time_warp + + self.opaque = opaque + + + def visit(self): + return [ self.child ] + + + def render(self, width, height, st, at): + + if self.rot_anim_timebase: + rot_time = at + else: + rot_time = st + + if self.zoom_anim_timebase: + zoom_time = at + else: + zoom_time = st + + if self.rot_delay == 0: + rot_time = 1.0 + else: + rot_time /= self.rot_delay + + if self.zoom_delay == 0: + zoom_time = 1.0 + else: + zoom_time /= self.zoom_delay + + if self.rot_repeat: + rot_time %= 1.0 + + if self.zoom_repeat: + zoom_time %= 1.0 + + if self.rot_bounce: + rot_time *= 2 + rot_time = min(rot_time, 2.0 - rot_time) + + if self.zoom_bounce: + zoom_time *= 2 + zoom_time = min(zoom_time, 2.0 - zoom_time) + + if renpy.game.less_updates: + rot_time = 1.0 + zoom_time = 1.0 + + rot_time = min(rot_time, 1.0) + zoom_time = min(zoom_time, 1.0) + + if self.rot_time_warp: + rot_time = self.rot_time_warp(rot_time) + + if self.zoom_time_warp: + zoom_time = self.zoom_time_warp(zoom_time) + + + angle = self.rot_start + (1.0 * self.rot_end - self.rot_start) * rot_time + zoom = self.zoom_start + (1.0 * self.zoom_end - self.zoom_start) * zoom_time + # angle = -angle * math.pi / 180 + + zoom = max(zoom, 0.001) + + if self.transform is None: + self.transform = Transform(self.child) + + self.transform.rotate = angle + self.transform.zoom = zoom + + rv = renpy.display.render.render(self.transform, width, height, st, at) + + if rot_time <= 1.0 or zoom_time <= 1.0: + renpy.display.render.redraw(self.transform, 0) + + return rv + + +# For compatibility with old games. +renpy.display.layout.Transform = Transform +renpy.display.layout.RotoZoom = RotoZoom +renpy.display.layout.SizeZoom = SizeZoom +renpy.display.layout.FactorZoom = FactorZoom +renpy.display.layout.Zoom = Zoom +renpy.display.layout.Revolver = Revolver +renpy.display.layout.Motion = Motion +renpy.display.layout.Interpolate = Interpolate + +# Leave these functions around - they might have been pickled somewhere. +renpy.display.layout.Revolve = Revolve # function +renpy.display.layout.Move = Move # function +renpy.display.layout.Pan = Pan # function |