summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/motion.py
diff options
context:
space:
mode:
authorAlex Xu <alex_y_xu@yahoo.ca>2013-08-22 22:45:26 -0400
committerAlex Xu <alex_y_xu@yahoo.ca>2013-08-22 22:45:26 -0400
commit718936110b9511631fa1f4396be992752bf8b719 (patch)
treea871768c06adc2959f8f0d69869532d36a95ffab /unrpyc/renpy/display/motion.py
parentece6cf9fbfdba9dac8d7bf98516a840c955a4853 (diff)
downloadhtml5ks-718936110b9511631fa1f4396be992752bf8b719.tar.xz
html5ks-718936110b9511631fa1f4396be992752bf8b719.zip
include renpy
Diffstat (limited to 'unrpyc/renpy/display/motion.py')
-rw-r--r--unrpyc/renpy/display/motion.py1526
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