summaryrefslogtreecommitdiff
path: root/unrpyc/renpy/display/transition.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/transition.py
parentece6cf9fbfdba9dac8d7bf98516a840c955a4853 (diff)
downloadhtml5ks-718936110b9511631fa1f4396be992752bf8b719.tar.xz
html5ks-718936110b9511631fa1f4396be992752bf8b719.zip
include renpy
Diffstat (limited to 'unrpyc/renpy/display/transition.py')
-rw-r--r--unrpyc/renpy/display/transition.py922
1 files changed, 922 insertions, 0 deletions
diff --git a/unrpyc/renpy/display/transition.py b/unrpyc/renpy/display/transition.py
new file mode 100644
index 0000000..7f93312
--- /dev/null
+++ b/unrpyc/renpy/display/transition.py
@@ -0,0 +1,922 @@
+# 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.
+
+# NOTE:
+# Transitions need to be able to work even when old_widget and new_widget
+# are None, at least to the point of making it through __init__. This is
+# so that prediction of images works.
+
+import renpy.display
+from renpy.display.render import render
+
+
+class Transition(renpy.display.core.Displayable):
+ """
+ This is the base class of most transitions. It takes care of event
+ dispatching.
+ """
+
+ def __init__(self, delay, **properties):
+ super(Transition, self).__init__(**properties)
+ self.delay = delay
+ self.events = True
+
+ def event(self, ev, x, y, st):
+
+ if self.events or ev.type == renpy.display.core.TIMEEVENT:
+ return self.new_widget.event(ev, x, y, st) # E1101
+ else:
+ return None
+
+ def visit(self):
+ return [ self.new_widget, self.old_widget ] # E1101
+
+
+def null_render(d, width, height, st, at):
+
+ d.events = True
+ surf = renpy.display.render.render(d.new_widget,
+ width,
+ height,
+ st, at)
+
+ rv = renpy.display.render.Render(surf.width, surf.height)
+ rv.blit(surf, (0, 0))
+
+ return rv
+
+class NoTransition(Transition):
+ """
+ :doc: transition function
+ :args: (delay)
+
+ Returns a transition that only displays the new screen for `delay` seconds.
+ It can be useful as part of a MultipleTransition.
+ """
+
+ def __init__(self, delay, old_widget=None, new_widget=None, **properties):
+ super(NoTransition, self).__init__(delay, **properties)
+
+ self.old_widget = old_widget
+ self.new_widget = new_widget
+ self.events = True
+
+ def render(self, width, height, st, at):
+ return null_render(self, width, height, st, at)
+
+
+class MultipleTransition(Transition):
+ """
+ :doc: transition function
+ :args: (args)
+
+ Returns a transition that allows multiple transitions to be displayed, one
+ after the other.
+
+ `args`
+ A list containing an odd number of items. The first, third, and
+ other odd-numbered items must be scenes, and the even items
+ must be transitions. A scene can be one of:
+
+ * A displayable.
+ * False, to use the old scene.
+ * True, to use the new scene.
+
+ Almost always, the first argument will be False and the last True.
+
+ The transitions in `args` are applied in order. For each transition,
+ the old scene is the screen preceding it, and the new scene is the
+ scene following it. For example::
+
+ define logodissolve = MultipleTransition(
+ False, Dissolve(0.5)
+ "logo.jpg", NoTransition(1.0),
+ "logo.jpg", dissolve,
+ True)
+
+ This example will dissolve to logo.jpg, wait 1 second, and then
+ dissolve to the new scene.
+ """
+
+ def __init__(self, args, old_widget=None, new_widget=None, **properties):
+
+ if len(args) % 2 != 1 or len(args) < 3:
+ raise Exception("MultipleTransition requires an odd number of arguments, and at least 3 arguments.")
+
+ self.transitions = [ ]
+
+ # The screens that we use for the transition.
+ self.screens = [ renpy.easy.displayable(i) for i in args[0::2] ]
+
+ def oldnew(w):
+ if w is False:
+ return old_widget
+ if w is True:
+ return new_widget
+
+ return w
+
+ for old, trans, new in zip(self.screens[0:], args[1::2], self.screens[1:]):
+ old = oldnew(old)
+ new = oldnew(new)
+
+ self.transitions.append(trans(old_widget=old, new_widget=new))
+
+ super(MultipleTransition, self).__init__(sum([i.delay for i in self.transitions]), **properties)
+
+ self.new_widget = self.transitions[-1]
+ self.events = False
+
+ def visit(self):
+ return [ i for i in self.screens if isinstance(i, renpy.display.core.Displayable)] + self.transitions
+
+ def event(self, ev, x, y, st):
+
+ if self.events or ev.type == renpy.display.core.TIMEEVENT:
+ return self.transitions[-1].event(ev, x, y, st)
+ else:
+ return None
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates:
+ return null_render(self, width, height, st, at)
+
+ for trans in self.transitions[:-1]:
+
+ if trans.delay > st:
+ break
+
+ st -= trans.delay
+
+ else:
+
+ trans = self.transitions[-1]
+ self.events = True
+
+ if trans is not self.transitions[-1]:
+ renpy.display.render.render(self.transitions[-1], width, height, 0, 0)
+
+ surf = renpy.display.render.render(trans, width, height, st, at)
+ width, height = surf.get_size()
+ rv = renpy.display.render.Render(width, height)
+ rv.blit(surf, (0, 0))
+
+ if st < trans.delay:
+ renpy.display.render.redraw(self, trans.delay - st)
+
+ return rv
+
+
+def Fade(out_time,
+ hold_time,
+ in_time,
+ old_widget=None,
+ new_widget=None,
+ color=None,
+ widget=None,
+ alpha=False,
+ ):
+
+ """
+ :doc: transition function
+ :args: (out_time, hold_time, in_time, color="#000")
+ :name: Fade
+
+ Returns a transition that takes `out_time` seconds to fade to
+ a screen filled with `color`, holds at that screen for `hold_time`
+ seconds, and then takes `in_time` to fade to then new screen.
+
+ ::
+
+ # Fade to black and back.
+ define fade = Fade(0.5, 0.0, 0.5)
+
+ # Hold at black for a bit.
+ define fadehold = Fade(0.5, 1.0, 0.5)
+
+ # Camera flash - quickly fades to white, then back to the scene.
+ define flash = Fade(0.1, 0.0, 0.5, color="#fff")
+ """
+
+ dissolve = renpy.curry.curry(Dissolve)
+ notrans = renpy.curry.curry(NoTransition)
+
+ widget = renpy.easy.displayable_or_none(widget)
+
+ if color:
+ widget = renpy.display.image.Solid(color)
+
+ if not widget:
+ widget = renpy.display.image.Solid((0, 0, 0, 255))
+
+ args = [ False, dissolve(out_time, alpha=alpha), widget ]
+
+ if hold_time:
+ args.extend([ notrans(hold_time), widget, ])
+
+ args.extend([dissolve(in_time, alpha=alpha), True ])
+
+ return MultipleTransition(args, old_widget=old_widget, new_widget=new_widget)
+
+
+class Pixellate(Transition):
+ """
+ :doc: transition function
+ :args: (time, steps)
+ :name: Pixellate
+
+ Returns a transition that pixellates out the old screen, and then
+ pixellates in the new screen.
+
+ `time`
+ The total time the transition will take, in seconds.
+
+ `steps`
+ The number of steps that will occur, in each direction. Each step
+ creates pixels about twice the size of those in the previous step,
+ so a 5-step pixellation will create 32x32 pixels.
+ """
+
+ def __init__(self, time, steps, old_widget=None, new_widget=None, **properties):
+
+ time = float(time)
+
+ super(Pixellate, self).__init__(time, **properties)
+
+ self.time = time
+ self.steps = steps
+
+ self.old_widget = old_widget
+ self.new_widget = new_widget
+
+ self.events = False
+
+ self.quantum = time / (2 * steps)
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates:
+ return null_render(self, width, height, st, at)
+
+ if st >= self.time:
+ self.events = True
+ return render(self.new_widget, width, height, st, at)
+
+ step = st // self.quantum + 1
+ visible = self.old_widget
+
+ if step > self.steps:
+ step = (self.steps * 2) - step + 1
+ visible = self.new_widget
+ self.events = True
+
+
+ rdr = render(visible, width, height, st, at)
+ rv = renpy.display.render.Render(rdr.width, rdr.height)
+
+ rv.blit(rdr, (0, 0))
+
+ rv.operation = renpy.display.render.PIXELLATE
+ rv.operation_parameter = 2 ** step
+
+ renpy.display.render.redraw(self, 0)
+
+ return rv
+
+
+class Dissolve(Transition):
+ """
+ :doc: transition function
+ :args: (time, alpha=False, time_warp=None)
+ :name: Dissolve
+
+ Returns a transition that dissolves from the old scene to the new scene.
+
+ `time`
+ The time the dissolve will take.
+
+ `alpha`
+ If true, the dissolve will alpha-composite the the result of the
+ transition with the screen. If false, the result of the transition
+ will replace the screen, which is more efficient.
+
+ `time_warp`
+ A function that adjusts the timeline. If not None, this should be a
+ function that takes a fractional time between 0.0 and 1.0, and returns
+ a number in the same range.
+ """
+
+ __version__ = 1
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.alpha = False
+
+ time_warp = None
+
+ def __init__(self, time, old_widget=None, new_widget=None, alpha=False, time_warp=None, **properties):
+ super(Dissolve, self).__init__(time, **properties)
+
+ self.time = time
+ self.old_widget = old_widget
+ self.new_widget = new_widget
+ self.events = False
+ self.alpha = alpha
+ self.time_warp = time_warp
+
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates:
+ return null_render(self, width, height, st, at)
+
+ if st >= self.time:
+ self.events = True
+ return render(self.new_widget, width, height, st, at)
+
+ complete = min(1.0, st / self.time)
+
+ if self.time_warp is not None:
+ complete = self.time_warp(complete)
+
+ bottom = render(self.old_widget, width, height, st, at)
+ top = render(self.new_widget, width, height, st, at)
+
+ width = min(top.width, bottom.width)
+ height = min(top.height, bottom.height)
+
+ rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+ rv.operation = renpy.display.render.DISSOLVE
+ rv.operation_alpha = self.alpha
+ rv.operation_complete = complete
+
+ rv.blit(bottom, (0, 0), focus=False, main=False)
+ rv.blit(top, (0, 0), focus=True, main=True)
+
+ renpy.display.render.redraw(self, 0)
+
+ return rv
+
+
+class ImageDissolve(Transition):
+ """
+ :doc: transition function
+ :args: (image, time, ramplen=8, reverse=False, alpha=True, time_warp=None)
+ :name: ImageDissolve
+
+ Returns a transition that dissolves the old scene into the new scene, using
+ an image to control the dissolve process. This means that white pixels will
+ dissolve in first, and black pixels will dissolve in last.
+
+ `image`
+ A control image to use. This must be either an image file or
+ image manipulator. The control image should be the size of
+ the scenes being dissolved.
+
+ `time`
+ The time the dissolve will take.
+
+ `ramplen`
+ The length of the ramp to use. This must be an integer power
+ of 2. When this is the default value of 8, when a white pixel
+ is fully dissolved, a pixel 8 shades of gray darker will have
+ completed one step of dissolving in.
+
+ `reverse`
+ If true, black pixels will dissolve in before white pixels.
+
+ `alpha`
+ If true, the dissolve will alpha-composite the the result of the
+ transition with the screen. If false, the result of the transition
+ will replace the screen, which is more efficient.
+
+ `time_warp`
+ A function that adjusts the timeline. If not None, this should be a
+ function that takes a fractional time between 0.0 and 1.0, and returns
+ a number in the same range.
+
+ ::
+
+ define circirisout = ImageDissolve("circiris.png", 1.0)
+ define circirisin = ImageDissolve("circiris.png", 1.0, reverse=True)
+ define circiristbigramp = ImageDissolve("circiris.png", 1.0, ramplen=256)
+ """
+
+ __version__ = 1
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.alpha = False
+
+ time_warp = None
+
+ def __init__(
+ self,
+ image,
+ time,
+ ramplen=8,
+ ramptype='linear',
+ ramp=None,
+ reverse=False,
+ alpha=False,
+ old_widget=None,
+ new_widget=None,
+ time_warp=None,
+ **properties):
+
+ # ramptype and ramp are now unused, but are kept for compatbility with
+ # older code.
+
+ super(ImageDissolve, self).__init__(time, **properties)
+
+ self.old_widget = old_widget
+ self.new_widget = new_widget
+ self.events = False
+ self.alpha = alpha
+ self.time_warp = time_warp
+
+ if not reverse:
+
+ # Copies red -> alpha
+ matrix = renpy.display.im.matrix(
+ 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 1,
+ 1, 0, 0, 0, 0)
+
+ else:
+
+ # Copies 1-red -> alpha
+ matrix = renpy.display.im.matrix(
+ 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 1,
+ - 1, 0, 0, 0, 1)
+
+ self.image = renpy.display.im.MatrixColor(image, matrix)
+
+ if ramp is not None:
+ ramplen = len(ramp)
+
+ # The length of the ramp.
+ self.ramplen = max(ramplen, 1)
+
+
+ def visit(self):
+ return super(ImageDissolve, self).visit() + [ self.image ]
+
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates or renpy.display.less_imagedissolve:
+ return null_render(self, width, height, st, at)
+
+ if st >= self.delay:
+ self.events = True
+ return render(self.new_widget, width, height, st, at)
+
+ image = render(self.image, width, height, st, at)
+ bottom = render(self.old_widget, width, height, st, at)
+ top = render(self.new_widget, width, height, st, at)
+
+ width = min(bottom.width, top.width, image.width)
+ height = min(bottom.height, top.height, image.height)
+
+ rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+ complete = st / self.delay
+
+ if self.time_warp is not None:
+ complete = self.time_warp(complete)
+
+ rv.operation = renpy.display.render.IMAGEDISSOLVE
+ rv.operation_alpha = self.alpha
+ rv.operation_complete = complete
+ rv.operation_parameter = self.ramplen
+
+ rv.blit(image, (0, 0), focus=False, main=False)
+ rv.blit(bottom, (0, 0), focus=False, main=False)
+ rv.blit(top, (0, 0), focus=True, main=True)
+
+ renpy.display.render.redraw(self, 0)
+
+ return rv
+
+
+class AlphaDissolve(Transition):
+ """
+ :doc: transition function
+ :args: (control, delay=0.0, alpha=False, reverse=False)
+
+ Returns a transition that uses a control displayable (almost always some
+ sort of animated transform) to transition from one screen to another. The
+ transform is evaluated. The new screen is used where the transform is
+ opaque, and the old image is used when it is transparent.
+
+ `control`
+ The control transform.
+
+ `delay`
+ The time the transition takes, before ending.
+
+ `alpha`
+ If true, the image is composited with what's behind it. If false,
+ the default, the image is opaque and overwrites what's behind it.
+
+ `reverse`
+ If true, the alpha channel is reversed. Opaque areas are taken
+ from the old image, while transparent areas are taken from the
+ new image.
+ """
+
+ def __init__(
+ self,
+ control,
+ delay=0.0,
+ old_widget=None,
+ new_widget=None,
+ alpha=False,
+ reverse=False,
+ **properties):
+
+ super(AlphaDissolve, self).__init__(delay, **properties)
+
+ self.control = renpy.display.layout.Fixed()
+ self.control.add(control)
+
+ self.old_widget = renpy.easy.displayable(old_widget)
+ self.new_widget = renpy.easy.displayable(new_widget)
+ self.events = False
+
+ self.alpha = alpha
+ self.reverse = reverse
+
+ def visit(self):
+ return super(AlphaDissolve, self).visit() + [ self.control ]
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates or renpy.display.less_imagedissolve:
+ return null_render(self, width, height, st, at)
+
+ if st >= self.delay:
+ self.events = True
+
+ bottom = render(self.old_widget, width, height, st, at)
+ top = render(self.new_widget, width, height, st, at)
+
+ width = min(bottom.width, top.width)
+ height = min(bottom.height, top.height)
+
+ control = render(self.control, width, height, st, at)
+
+ rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+ rv.operation = renpy.display.render.IMAGEDISSOLVE
+ rv.operation_alpha = self.alpha
+ rv.operation_complete = 256.0 / (256.0 + 256.0)
+ rv.operation_parameter = 256
+
+ rv.blit(control, (0, 0), focus=False, main=False)
+
+ if not self.reverse:
+ rv.blit(bottom, (0, 0), focus=False, main=False)
+ rv.blit(top, (0, 0), focus=True, main=True)
+ else:
+ rv.blit(top, (0, 0), focus=True, main=True)
+ rv.blit(bottom, (0, 0), focus=False, main=False)
+
+ return rv
+
+
+class CropMove(Transition):
+ """
+ :doc: transition function
+ :args: (time, mode="slideright", startcrop=(0.0, 0.0, 0.0, 1.0), startpos=(0.0, 0.0), endcrop=(0.0, 0.0, 1.0, 1.0), endpos=(0.0, 0.0), topnew=True)
+ :name: CropMove
+
+ Returns a transition that works by cropping a scene and positioning it on the
+ screen. This can be used to implement a variety of effects, all of which
+ involved changing rectangular slices of scenes.
+
+ `time`
+ The time the transition takes.
+
+ `mode`
+ The name of the mode of the transition. There are three groups
+ of modes: wipes, slides, and other. This can also be "custom",
+ to allow a custom mode to be defined.
+
+ In a wipe, the image stays fixed, and more of it is revealed as
+ the transition progresses. For example, in "wiperight", a wipe from left to right, first the left edge of the image is
+ revealed at the left edge of the screen, then the center of the image,
+ and finally the right side of the image at the right of the screen.
+ Other supported wipes are "wipeleft", "wipedown", and "wipeup".
+
+ In a slide, the image moves. So in a "slideright", the right edge of the
+ image starts at the left edge of the screen, and moves to the right
+ as the transition progresses. Other slides are "slideleft", "slidedown",
+ and "slideup".
+
+ There are also slideaways, in which the old image moves on top of
+ the new image. Slideaways include "slideawayright", "slideawayleft",
+ "slideawayup", and "slideawaydown".
+
+ We also support a rectangular iris in with "irisin" and a
+ rectangular iris out with "irisout".
+
+ The following parameters are only respected if the mode is "custom". Positions
+ are relative to the size of the screen, while the crops are relative to the
+ size of the image. So a crop of (0.25, 0.0, 0.5, 1.0) takes the middle
+ half of an image.
+
+ `startcrop`
+ The starting rectangle that is cropped out of the
+ top image. A 4-element tuple containing x, y, width, and height.
+
+ `startpos`
+ The starting place that the top image is drawn
+ to the screen at, a 2-element tuple containing x and y.
+
+ `endcrop`
+ The ending rectangle that is cropped out of the
+ top image. A 4-element tuple containing x, y, width, and height.
+
+ `endpos`
+ The ending place that the top image is drawn
+ to the screen at, a 2-element tuple containing x and y.
+
+ `topnew`
+ If true, the scene that is cropped and moved (and is on top of
+ the other scene) is the new scene. If false, it is the old scene.
+
+ ::
+
+ define wiperight = CropMove(1.0, "wiperight")
+ define wipeleft = CropMove(1.0, "wipeleft")
+ define wipeup = CropMove(1.0, "wipeup")
+ define wipedown = CropMove(1.0, "wipedown")
+
+ define slideright = CropMove(1.0, "slideright")
+ define slideleft = CropMove(1.0, "slideleft")
+ define slideup = CropMove(1.0, "slideup")
+ define slidedown = CropMove(1.0, "slidedown")
+
+ define slideawayright = CropMove(1.0, "slideawayright")
+ define slideawayleft = CropMove(1.0, "slideawayleft")
+ define slideawayup = CropMove(1.0, "slideawayup")
+ define slideawaydown = CropMove(1.0, "slideawaydown")
+
+ define irisout = CropMove(1.0, "irisout")
+ define irisin = CropMove(1.0, "irisin")
+ """
+
+ def __init__(self, time,
+ mode="slideright",
+ startcrop=(0.0, 0.0, 0.0, 1.0),
+ startpos=(0.0, 0.0),
+ endcrop=(0.0, 0.0, 1.0, 1.0),
+ endpos=(0.0, 0.0),
+ topnew=True,
+ old_widget=None,
+ new_widget=None,
+ **properties):
+
+ super(CropMove, self).__init__(time, **properties)
+ self.time = time
+
+ if mode == "wiperight":
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 0.0, 1.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "wipeleft":
+ startpos = (1.0, 0.0)
+ startcrop = (1.0, 0.0, 0.0, 1.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "wipedown":
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 0.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "wipeup":
+ startpos = (0.0, 1.0)
+ startcrop = (0.0, 1.0, 1.0, 0.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "slideright":
+ startpos = (0.0, 0.0)
+ startcrop = (1.0, 0.0, 0.0, 1.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "slideleft":
+ startpos = (1.0, 0.0)
+ startcrop = (0.0, 0.0, 0.0, 1.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "slideup":
+ startpos = (0.0, 1.0)
+ startcrop = (0.0, 0.0, 1.0, 0.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "slidedown":
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 1.0, 1.0, 0.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "slideawayleft":
+ endpos = (0.0, 0.0)
+ endcrop = (1.0, 0.0, 0.0, 1.0)
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = False
+
+ elif mode == "slideawayright":
+ endpos = (1.0, 0.0)
+ endcrop = (0.0, 0.0, 0.0, 1.0)
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = False
+
+ elif mode == "slideawaydown":
+ endpos = (0.0, 1.0)
+ endcrop = (0.0, 0.0, 1.0, 0.0)
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = False
+
+ elif mode == "slideawayup":
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 1.0, 1.0, 0.0)
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = False
+
+ elif mode == "irisout":
+ startpos = (0.5, 0.5)
+ startcrop = (0.5, 0.5, 0.0, 0.0)
+ endpos = (0.0, 0.0)
+ endcrop = (0.0, 0.0, 1.0, 1.0)
+ topnew = True
+
+ elif mode == "irisin":
+ startpos = (0.0, 0.0)
+ startcrop = (0.0, 0.0, 1.0, 1.0)
+ endpos = (0.5, 0.5)
+ endcrop = (0.5, 0.5, 0.0, 0.0)
+ topnew = False
+
+
+ elif mode == "custom":
+ pass
+ else:
+ raise Exception("Invalid mode %s passed into CropMove." % mode)
+
+ self.delay = time
+ self.time = time
+
+ self.startpos = startpos
+ self.endpos = endpos
+
+ self.startcrop = startcrop
+ self.endcrop = endcrop
+
+ self.topnew = topnew
+
+ self.old_widget = old_widget
+ self.new_widget = new_widget
+
+ self.events = False
+
+ if topnew:
+ self.bottom = old_widget
+ self.top = new_widget
+ else:
+ self.bottom = new_widget
+ self.top = old_widget
+
+ def render(self, width, height, st, at):
+
+ if renpy.game.less_updates:
+ return null_render(self, width, height, st, at)
+
+ time = 1.0 * st / self.time
+
+ # Done rendering.
+ if time >= 1.0:
+ self.events = True
+ return render(self.new_widget, width, height, st, at)
+
+ # How we scale each element of a tuple.
+ scales = (width, height, width, height)
+
+ def interpolate_tuple(t0, t1):
+ return tuple([ int(s * (a * (1.0 - time) + b * time))
+ for a, b, s in zip(t0, t1, scales) ])
+
+ crop = interpolate_tuple(self.startcrop, self.endcrop)
+ pos = interpolate_tuple(self.startpos, self.endpos)
+
+
+ top = render(self.top, width, height, st, at)
+ bottom = render(self.bottom, width, height, st, at)
+
+ width = min(bottom.width, width)
+ height = min(bottom.height, height)
+ rv = renpy.display.render.Render(width, height)
+
+ rv.blit(bottom, (0, 0), focus=not self.topnew)
+
+ ss = top.subsurface(crop, focus=self.topnew)
+ rv.blit(ss, pos, focus=self.topnew)
+
+ renpy.display.render.redraw(self, 0)
+ return rv
+
+
+def ComposeTransition(trans, before=None, after=None, new_widget=None, old_widget=None):
+ """
+ :doc: transition function
+ :args: (trans, before, after)
+
+ Returns a transition that composes up to three transitions. If not None,
+ the `before` and `after` transitions are applied to the old and new
+ scenes, respectively. These updated old and new scenes are then supplied
+ to the `trans` transition.
+
+ ::
+
+ # Move the images in and out while dissolving. (This is a fairly expensive transition.)
+ define moveinoutdissolve = ComposeTransition(dissolve, before=moveoutleft, after=moveinright)
+ """
+
+ if before is not None:
+ old = before(new_widget=new_widget, old_widget=old_widget)
+ else:
+ old = old_widget
+
+ if after is not None:
+ new = after(new_widget=new_widget, old_widget=old_widget)
+ else:
+ new = new_widget
+
+ return trans(new_widget=new, old_widget=old)
+
+
+def SubTransition(rect, trans, old_widget=None, new_widget=None, **properties):
+ """
+ Applies a transition to a subset of the screen. Not documented.
+ """
+
+ x, y, _w, _h = rect
+
+ old = renpy.display.layout.LiveCrop(rect, old_widget)
+ new = renpy.display.layout.LiveCrop(rect, new_widget)
+
+ inner = trans(old_widget=old, new_widget=new)
+ delay = inner.delay
+ inner = renpy.display.layout.Position(inner, xpos=x, ypos=y, xanchor=0, yanchor=0)
+
+ f = renpy.display.layout.MultiBox(layout='fixed')
+ f.add(new_widget)
+ f.add(inner)
+
+ return NoTransition(delay, old_widget=f, new_widget=f)
+