summaryrefslogtreecommitdiff
path: root/unrpyc
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
parentece6cf9fbfdba9dac8d7bf98516a840c955a4853 (diff)
downloadhtml5ks-718936110b9511631fa1f4396be992752bf8b719.tar.xz
html5ks-718936110b9511631fa1f4396be992752bf8b719.zip
include renpy
Diffstat (limited to 'unrpyc')
-rw-r--r--unrpyc/LICENSE3
-rw-r--r--unrpyc/README7
-rw-r--r--unrpyc/renpy/LICENSE.txt1024
-rw-r--r--unrpyc/renpy/README1
-rw-r--r--unrpyc/renpy/__init__.py278
-rw-r--r--unrpyc/renpy/ast.py1757
-rw-r--r--unrpyc/renpy/atl.py1544
-rw-r--r--unrpyc/renpy/display/__init__.py39
-rw-r--r--unrpyc/renpy/display/accelerator.pyx296
-rw-r--r--unrpyc/renpy/display/anim.py634
-rw-r--r--unrpyc/renpy/display/behavior.py1531
-rw-r--r--unrpyc/renpy/display/core.py2463
-rw-r--r--unrpyc/renpy/display/dragdrop.py731
-rw-r--r--unrpyc/renpy/display/error.py159
-rw-r--r--unrpyc/renpy/display/focus.py449
-rw-r--r--unrpyc/renpy/display/im.py1562
-rw-r--r--unrpyc/renpy/display/image.py397
-rw-r--r--unrpyc/renpy/display/imagelike.py382
-rw-r--r--unrpyc/renpy/display/imagemap.py233
-rw-r--r--unrpyc/renpy/display/joystick.py126
-rw-r--r--unrpyc/renpy/display/layout.py1744
-rw-r--r--unrpyc/renpy/display/minigame.py25
-rw-r--r--unrpyc/renpy/display/module.py275
-rw-r--r--unrpyc/renpy/display/motion.py1526
-rw-r--r--unrpyc/renpy/display/movetransition.py640
-rw-r--r--unrpyc/renpy/display/particle.py615
-rw-r--r--unrpyc/renpy/display/pgrender.py167
-rw-r--r--unrpyc/renpy/display/predict.py156
-rw-r--r--unrpyc/renpy/display/presplash.py113
-rw-r--r--unrpyc/renpy/display/render.pxd47
-rw-r--r--unrpyc/renpy/display/render.pyx1174
-rw-r--r--unrpyc/renpy/display/scale.py109
-rw-r--r--unrpyc/renpy/display/screen.py639
-rw-r--r--unrpyc/renpy/display/swdraw.py1102
-rw-r--r--unrpyc/renpy/display/transition.py922
-rw-r--r--unrpyc/renpy/display/video.py234
-rw-r--r--unrpyc/renpy/game.py437
-rw-r--r--unrpyc/renpy/log.py152
-rw-r--r--unrpyc/renpy/object.py60
39 files changed, 23748 insertions, 5 deletions
diff --git a/unrpyc/LICENSE b/unrpyc/LICENSE
index 8a8ff9b..0279e04 100644
--- a/unrpyc/LICENSE
+++ b/unrpyc/LICENSE
@@ -1,3 +1,6 @@
+The renpy directory is also distributed under the MIT license located at
+renpy/LICENSE.txt.
+
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
diff --git a/unrpyc/README b/unrpyc/README
index ba1791f..6c337f6 100644
--- a/unrpyc/README
+++ b/unrpyc/README
@@ -4,11 +4,8 @@ It's made for Katawa Shoujo script files; trying to use it on anything else will
How to use:
-1. Copy Ren'Py distribution into "renpy" folder.
-2. Execute "2to3 -w renpy/*.py".
-3. Put the rpyc files from the official KS distribution in here.
-4. Run "make" with appropriate -j options (yay auto-parallelization)
-5. Run "make install".
+1. Put the rpyc files from the official KS distribution in here.
+2. Run "make install".
If you're on Windows, sucks for you. Use a better OS.
diff --git a/unrpyc/renpy/LICENSE.txt b/unrpyc/renpy/LICENSE.txt
new file mode 100644
index 0000000..383a780
--- /dev/null
+++ b/unrpyc/renpy/LICENSE.txt
@@ -0,0 +1,1024 @@
+.. highlight:: none
+
+=======
+License
+=======
+
+Most of Ren'Py is covered by the terms of the following (MIT) license:
+
+ 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.
+
+Portions of Ren'Py are derived from code that is copyright using the
+Lesser GNU Public License, so Ren'Py games must be distributed in a
+manner that satisfies the LGPL.
+
+Please see each individual source file for a list of copyright
+holders. The artwork in the demo is released by various copyright
+holders, under the same terms.
+
+Ren'Py binaries include code from the following projects:
+
+* Python (Python License)
+* Pygame (LGPL)
+* SDL (LGPL)
+* SDL_image (LGPL)
+* SDL_ttf (LGPL)
+* Freetype (LGPL)
+* Fribidi (LGPL)
+* libav (LGPL)
+* libjpeg-turbo (LGPL)
+* libpng (PNG license)
+* zlib (Zlib License)
+* bzip2 (Bzip2 License)
+* pyobjc (MIT License)
+* py2exe (MIT License)
+* GLEW (Modified BSD, MIT)
+* zsync (Artistic License)
+
+For the purpose of LGPL compliance, the source code to all LGPL
+software we depend on is either in the Ren'Py package (available from
+http://www.renpy.org/), or in the renpy-deps package
+(http://www.renpy.org/dl/lgpl/). We believe compliance can be achieved
+by including a copy of this license with every copy of Ren'Py you
+distribute, and referring to it in your project's README file.
+
+Ren'Py may be distributed alongside the jEdit or Editra text
+editors. Editra is licensed under the wxWindows license, while
+jEdit is under the GNU General Public License.
+
+
+GNU Lesser General Public License
+=================================
+
+::
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ [This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+ freedom to share and change it. By contrast, the GNU General Public
+ Licenses are intended to guarantee your freedom to share and change
+ free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+ specially designated software packages--typically libraries--of the
+ Free Software Foundation and other authors who decide to use it. You
+ can use it too, but we suggest you first think carefully about whether
+ this license or the ordinary General Public License is the better
+ strategy to use in any particular case, based on the explanations
+ below.
+
+ When we speak of free software, we are referring to freedom of use,
+ not price. Our General Public Licenses are designed to make sure that
+ you have the freedom to distribute copies of free software (and charge
+ for this service if you wish); that you receive source code or can get
+ it if you want it; that you can change the software and use pieces of
+ it in new free programs; and that you are informed that you can do
+ these things.
+
+ To protect your rights, we need to make restrictions that forbid
+ distributors to deny you these rights or to ask you to surrender these
+ rights. These restrictions translate to certain responsibilities for
+ you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+ or for a fee, you must give the recipients all the rights that we gave
+ you. You must make sure that they, too, receive or can get the source
+ code. If you link other code with the library, you must provide
+ complete object files to the recipients, so that they can relink them
+ with the library after making changes to the library and recompiling
+ it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+ library, and (2) we offer you this license, which gives you legal
+ permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+ there is no warranty for the free library. Also, if the library is
+ modified by someone else and passed on, the recipients should know
+ that what they have is not the original version, so that the original
+ author's reputation will not be affected by problems that might be
+ introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+ any free program. We wish to make sure that a company cannot
+ effectively restrict the users of a free program by obtaining a
+ restrictive license from a patent holder. Therefore, we insist that
+ any patent license obtained for a version of the library must be
+ consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ ordinary GNU General Public License. This license, the GNU Lesser
+ General Public License, applies to certain designated libraries, and
+ is quite different from the ordinary General Public License. We use
+ this license for certain libraries in order to permit linking those
+ libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+ a shared library, the combination of the two is legally speaking a
+ combined work, a derivative of the original library. The ordinary
+ General Public License therefore permits such linking only if the
+ entire combination fits its criteria of freedom. The Lesser General
+ Public License permits more lax criteria for linking other code with
+ the library.
+
+ We call this license the "Lesser" General Public License because it
+ does Less to protect the user's freedom than the ordinary General
+ Public License. It also provides other free software developers Less
+ of an advantage over competing non-free programs. These disadvantages
+ are the reason we use the ordinary General Public License for many
+ libraries. However, the Lesser license provides advantages in certain
+ special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+ encourage the widest possible use of a certain library, so that it
+ becomes a de-facto standard. To achieve this, non-free programs must
+ be allowed to use the library. A more frequent case is that a free
+ library does the same job as widely used non-free libraries. In this
+ case, there is little to gain by limiting the free library to free
+ software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+ programs enables a greater number of people to use a large body of
+ free software. For example, permission to use the GNU C Library in
+ non-free programs enables many more people to use the whole GNU
+ operating system, as well as its variant, the GNU/Linux operating
+ system.
+
+ Although the Lesser General Public License is Less protective of the
+ users' freedom, it does ensure that the user of a program that is
+ linked with the Library has the freedom and the wherewithal to run
+ that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+ modification follow. Pay close attention to the difference between a
+ "work based on the library" and a "work that uses the library". The
+ former contains code derived from the library, whereas the latter must
+ be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+ program which contains a notice placed by the copyright holder or
+ other authorized party saying it may be distributed under the terms of
+ this Lesser General Public License (also called "this License").
+ Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+ prepared so as to be conveniently linked with application programs
+ (which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+ which has been distributed under these terms. A "work based on the
+ Library" means either the Library or any derivative work under
+ copyright law: that is to say, a work containing the Library or a
+ portion of it, either verbatim or with modifications and/or translated
+ straightforwardly into another language. (Hereinafter, translation is
+ included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+ making modifications to it. For a library, complete source code means
+ all the source code for all modules it contains, plus any associated
+ interface definition files, plus the scripts used to control
+ compilation and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+ covered by this License; they are outside its scope. The act of
+ running a program using the Library is not restricted, and output from
+ such a program is covered only if its contents constitute a work based
+ on the Library (independent of the use of the Library in a tool for
+ writing it). Whether that is true depends on what the Library does
+ and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+ complete source code as you receive it, in any medium, provided that
+ you conspicuously and appropriately publish on each copy an
+ appropriate copyright notice and disclaimer of warranty; keep intact
+ all the notices that refer to this License and to the absence of any
+ warranty; and distribute a copy of this License along with the
+ Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+ and you may at your option offer warranty protection in exchange for a
+ fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+ of it, thus forming a work based on the Library, and copy and
+ distribute such modifications or work under the terms of Section 1
+ above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+ These requirements apply to the modified work as a whole. If
+ identifiable sections of that work are not derived from the Library,
+ and can be reasonably considered independent and separate works in
+ themselves, then this License, and its terms, do not apply to those
+ sections when you distribute them as separate works. But when you
+ distribute the same sections as part of a whole which is a work based
+ on the Library, the distribution of the whole must be on the terms of
+ this License, whose permissions for other licensees extend to the
+ entire whole, and thus to each and every part regardless of who wrote
+ it.
+
+ Thus, it is not the intent of this section to claim rights or contest
+ your rights to work written entirely by you; rather, the intent is to
+ exercise the right to control the distribution of derivative or
+ collective works based on the Library.
+
+ In addition, mere aggregation of another work not based on the Library
+ with the Library (or with a work based on the Library) on a volume of
+ a storage or distribution medium does not bring the other work under
+ the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+ License instead of this License to a given copy of the Library. To do
+ this, you must alter all the notices that refer to this License, so
+ that they refer to the ordinary GNU General Public License, version 2,
+ instead of to this License. (If a newer version than version 2 of the
+ ordinary GNU General Public License has appeared, then you can specify
+ that version instead if you wish.) Do not make any other change in
+ these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+ that copy, so the ordinary GNU General Public License applies to all
+ subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+ the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+ derivative of it, under Section 2) in object code or executable form
+ under the terms of Sections 1 and 2 above provided that you accompany
+ it with the complete corresponding machine-readable source code, which
+ must be distributed under the terms of Sections 1 and 2 above on a
+ medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+ from a designated place, then offering equivalent access to copy the
+ source code from the same place satisfies the requirement to
+ distribute the source code, even though third parties are not
+ compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+ Library, but is designed to work with the Library by being compiled or
+ linked with it, is called a "work that uses the Library". Such a
+ work, in isolation, is not a derivative work of the Library, and
+ therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+ creates an executable that is a derivative of the Library (because it
+ contains portions of the Library), rather than a "work that uses the
+ library". The executable is therefore covered by this License.
+ Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+ that is part of the Library, the object code for the work may be a
+ derivative work of the Library even though the source code is not.
+ Whether this is true is especially significant if the work can be
+ linked without the Library, or if the work is itself a library. The
+ threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+ structure layouts and accessors, and small macros and small inline
+ functions (ten lines or less in length), then the use of the object
+ file is unrestricted, regardless of whether it is legally a derivative
+ work. (Executables containing this object code plus portions of the
+ Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+ distribute the object code for the work under the terms of Section 6.
+ Any executables containing that work also fall under Section 6,
+ whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+ link a "work that uses the Library" with the Library to produce a
+ work containing portions of the Library, and distribute that work
+ under terms of your choice, provided that the terms permit
+ modification of the work for the customer's own use and reverse
+ engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+ Library is used in it and that the Library and its use are covered by
+ this License. You must supply a copy of this License. If the work
+ during execution displays copyright notices, you must include the
+ copyright notice for the Library among them, as well as a reference
+ directing the user to the copy of this License. Also, you must do one
+ of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at least
+ three years, to give the same user the materials specified in
+ Subsection 6a, above, for a charge no more than the cost of
+ performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+ Library" must include any data and utility programs needed for
+ reproducing the executable from it. However, as a special exception,
+ the materials to be distributed need not include anything that is
+ normally distributed (in either source or binary form) with the major
+ components (compiler, kernel, and so on) of the operating system on
+ which the executable runs, unless that component itself accompanies
+ the executable.
+
+ It may happen that this requirement contradicts the license
+ restrictions of other proprietary libraries that do not normally
+ accompany the operating system. Such a contradiction means you cannot
+ use both them and the Library together in an executable that you
+ distribute.
+
+ 7. You may place library facilities that are a work based on the
+ Library side-by-side in a single library together with other library
+ facilities not covered by this License, and distribute such a combined
+ library, provided that the separate distribution of the work based on
+ the Library and of the other library facilities is otherwise
+ permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+ the Library except as expressly provided under this License. Any
+ attempt otherwise to copy, modify, sublicense, link with, or
+ distribute the Library is void, and will automatically terminate your
+ rights under this License. However, parties who have received copies,
+ or rights, from you under this License will not have their licenses
+ terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+ signed it. However, nothing else grants you permission to modify or
+ distribute the Library or its derivative works. These actions are
+ prohibited by law if you do not accept this License. Therefore, by
+ modifying or distributing the Library (or any work based on the
+ Library), you indicate your acceptance of this License to do so, and
+ all its terms and conditions for copying, distributing or modifying
+ the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+ Library), the recipient automatically receives a license from the
+ original licensor to copy, distribute, link with or modify the Library
+ subject to these terms and conditions. You may not impose any further
+ restrictions on the recipients' exercise of the rights granted herein.
+ You are not responsible for enforcing compliance by third parties with
+ this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+ infringement or for any other reason (not limited to patent issues),
+ conditions are imposed on you (whether by court order, agreement or
+ otherwise) that contradict the conditions of this License, they do not
+ excuse you from the conditions of this License. If you cannot
+ distribute so as to satisfy simultaneously your obligations under this
+ License and any other pertinent obligations, then as a consequence you
+ may not distribute the Library at all. For example, if a patent
+ license would not permit royalty-free redistribution of the Library by
+ all those who receive copies directly or indirectly through you, then
+ the only way you could satisfy both it and this License would be to
+ refrain entirely from distribution of the Library.
+
+ If any portion of this section is held invalid or unenforceable under
+ any particular circumstance, the balance of the section is intended to
+ apply, and the section as a whole is intended to apply in other
+ circumstances.
+
+ It is not the purpose of this section to induce you to infringe any
+ patents or other property right claims or to contest validity of any
+ such claims; this section has the sole purpose of protecting the
+ integrity of the free software distribution system which is
+ implemented by public license practices. Many people have made
+ generous contributions to the wide range of software distributed
+ through that system in reliance on consistent application of that
+ system; it is up to the author/donor to decide if he or she is willing
+ to distribute software through any other system and a licensee cannot
+ impose that choice.
+
+ This section is intended to make thoroughly clear what is believed to
+ be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+ certain countries either by patents or by copyrighted interfaces, the
+ original copyright holder who places the Library under this License
+ may add an explicit geographical distribution limitation excluding those
+ countries, so that distribution is permitted only in or among
+ countries not thus excluded. In such case, this License incorporates
+ the limitation as if written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+ versions of the Lesser General Public License from time to time.
+ Such new versions will be similar in spirit to the present version,
+ but may differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the Library
+ specifies a version number of this License which applies to it and
+ "any later version", you have the option of following the terms and
+ conditions either of that version or of any later version published by
+ the Free Software Foundation. If the Library does not specify a
+ license version number, you may choose any version ever published by
+ the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+ programs whose distribution conditions are incompatible with these,
+ write to the author to ask for permission. For software which is
+ copyrighted by the Free Software Foundation, write to the Free
+ Software Foundation; we sometimes make exceptions for this. Our
+ decision will be guided by the two goals of preserving the free status
+ of all derivatives of our free software and of promoting the sharing
+ and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+ WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+ EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+ OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+ KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+ LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+ THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+ WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+ AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+ FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+ CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+ LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+ SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+ DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+ possible use to the public, we recommend making it free software that
+ everyone can redistribute and change. You can do so by permitting
+ redistribution under these terms (or, alternatively, under the terms
+ of the ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library.
+ It is safest to attach them to the start of each source file to most
+ effectively convey the exclusion of warranty; and each file should
+ have at least the "copyright" line and a pointer to where the full
+ notice is found.
+
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+ Also add information on how to contact you by electronic and paper mail.
+
+ You should also get your employer (if you work as a programmer) or
+ your school, if any, to sign a "copyright disclaimer" for the library,
+ if necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James
+ Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+ That's all there is to it!
+
+
+Python License
+==============
+
+::
+
+ 1. This LICENSE AGREEMENT is between the Python Software Foundation
+ ("PSF"), and the Individual or Organization ("Licensee") accessing and
+ otherwise using Python 2.3 software in source or binary form and its
+ associated documentation.
+
+ 2. Subject to the terms and conditions of this License Agreement, PSF
+ hereby grants Licensee a nonexclusive, royalty-free, world-wide
+ license to reproduce, analyze, test, perform and/or display publicly,
+ prepare derivative works, distribute, and otherwise use Python 2.3
+ alone or in any derivative version, provided, however, that PSF's
+ License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+ 2001, 2002 Python Software Foundation; All Rights Reserved" are
+ retained in Python 2.3 alone or in any derivative version prepared by
+ Licensee.
+
+ 3. In the event Licensee prepares a derivative work that is based on
+ or incorporates Python 2.3 or any part thereof, and wants to make
+ the derivative work available to others as provided herein, then
+ Licensee hereby agrees to include in any such work a brief summary of
+ the changes made to Python 2.3.
+
+ 4. PSF is making Python 2.3 available to Licensee on an "AS IS"
+ basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+ IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+ DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+ FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.3 WILL NOT
+ INFRINGE ANY THIRD PARTY RIGHTS.
+
+ 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+ 2.3 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+ A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.3,
+ OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+ 6. This License Agreement will automatically terminate upon a material
+ breach of its terms and conditions.
+
+ 7. Nothing in this License Agreement shall be deemed to create any
+ relationship of agency, partnership, or joint venture between PSF and
+ Licensee. This License Agreement does not grant permission to use PSF
+ trademarks or trade name in a trademark sense to endorse or promote
+ products or services of Licensee, or any third party.
+
+ 8. By copying, installing or otherwise using Python 2.3, Licensee
+ agrees to be bound by the terms and conditions of this License
+ Agreement.
+
+Jpeg License
+============
+
+::
+
+ In plain English:
+
+ 1. We don't promise that this software works. (But if you find any bugs,
+ please let us know!)
+ 2. You can use this software for whatever you want. You don't have to pay us.
+ 3. You may not pretend that you wrote this software. If you use it in a
+ program, you must acknowledge somewhere in your documentation that
+ you've used the IJG code.
+
+ In legalese:
+
+ The authors make NO WARRANTY or representation, either express or implied,
+ with respect to this software, its quality, accuracy, merchantability, or
+ fitness for a particular purpose. This software is provided "AS IS", and you,
+ its user, assume the entire risk as to its quality and accuracy.
+
+ This software is copyright (C) 1991-1998, Thomas G. Lane.
+ All Rights Reserved except as specified below.
+
+ Permission is hereby granted to use, copy, modify, and distribute this
+ software (or portions thereof) for any purpose, without fee, subject to these
+ conditions:
+ (1) If any part of the source code for this software is distributed, then this
+ README file must be included, with this copyright and no-warranty notice
+ unaltered; and any additions, deletions, or changes to the original files
+ must be clearly indicated in accompanying documentation.
+ (2) If only executable code is distributed, then the accompanying
+ documentation must state that "this software is based in part on the work of
+ the Independent JPEG Group".
+ (3) Permission for use of this software is granted only if the user accepts
+ full responsibility for any undesirable consequences; the authors accept
+ NO LIABILITY for damages of any kind.
+
+ These conditions apply to any software derived from or based on the IJG code,
+ not just to the unmodified library. If you use our work, you ought to
+ acknowledge us.
+
+ Permission is NOT granted for the use of any IJG author's name or company name
+ in advertising or publicity relating to this software or products derived from
+ it. This software may be referred to only as "the Independent JPEG Group's
+ software".
+
+ We specifically permit and encourage the use of this software as the basis of
+ commercial products, provided that all warranty or liability claims are
+ assumed by the product vendor.
+
+
+PNG License
+===========
+
+::
+
+ The PNG Reference Library is supplied "AS IS". The Contributing Authors
+ and Group 42, Inc. disclaim all warranties, expressed or implied,
+ including, without limitation, the warranties of merchantability and of
+ fitness for any purpose. The Contributing Authors and Group 42, Inc.
+ assume no liability for direct, indirect, incidental, special, exemplary,
+ or consequential damages, which may result from the use of the PNG
+ Reference Library, even if advised of the possibility of such damage.
+
+ Permission is hereby granted to use, copy, modify, and distribute this
+ source code, or portions hereof, for any purpose, without fee, subject
+ to the following restrictions:
+
+ 1. The origin of this source code must not be misrepresented.
+
+ 2. Altered versions must be plainly marked as such and must not
+ be misrepresented as being the original source.
+
+ 3. This Copyright notice may not be removed or altered from any
+ source or altered source distribution.
+
+ The Contributing Authors and Group 42, Inc. specifically permit, without
+ fee, and encourage the use of this source code as a component to
+ supporting the PNG file format in commercial products. If you use this
+ source code in a product, acknowledgment is not required but would be
+ appreciated.
+
+Zlib License
+============
+
+::
+
+ This software is provided 'as-is', without any express or implied
+ warranty. In no event will the authors be held liable for any damages
+ arising from the use of this software.
+
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software
+ in a product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not be
+ misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source distribution.
+
+
+Bzip2 License
+=============
+
+::
+
+ This program, "bzip2", the associated library "libbzip2", and all
+ documentation, are copyright (C) 1996-2005 Julian R Seward. All
+ rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. The origin of this software must not be misrepresented; you must
+ not claim that you wrote the original software. If you use this
+ software in a product, an acknowledgment in the product
+ documentation would be appreciated but is not required.
+
+ 3. Altered source versions must be plainly marked as such, and must
+ not be misrepresented as being the original software.
+
+ 4. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Modified BSD License
+====================
+
+::
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ * The name of the author may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Artistic License
+================
+
+::
+
+ The Artistic License
+ Version 2.0beta4, October 2000
+
+ Copyright (C) 2000, Larry Wall.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ This copyright license states the terms under which a given free
+ software Package may be copied, modified and/or redistributed, while the
+ Originator(s) maintain some artistic control over the future development
+ of that Package (at least as much artistic control as can be given under
+ copyright law while still making the Package open source and free software).
+
+ This license is bound by copyright law, and thus it legally applies only
+ to works which the copyright holder has permitted copying, distribution
+ or modification under the terms of the Artistic License, Version 2.0.
+
+ You are reminded that You are always permitted to make arrangements
+ wholly outside of a given copyright license directly with the copyright
+ holder(s) of a given Package. If the terms of this license impede your
+ ability to make full use of the Package, You are encouraged to contact
+ the copyright holder(s) and seek a different licensing arrangement.
+
+ Definitions
+
+ "Package" refers to the collection of files distributed by the
+ Originator(s), and derivatives of that collection of files created
+ through textual modification.
+
+ "Standard Version" refers to the Package if it has not been modified, or
+ has been modified only in ways suggested by the Originator(s).
+
+ "Modified Version" refers to the Package, if it has been changed by You
+ via textual modification of the source code, and such changes were not
+ suggested by the Originator(s).
+
+ "Originator" refers to the author(s) and/or copyright holder(s) of the
+ Standard Version of the Package.
+
+ "You" and "Your" refers to any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Distribution Fee" is any fee that You charge for providing a copy of
+ this Package to another party. It does not refer to licensing fees.
+
+ "Freely Available" means that:
+
+ (a) no fee is charged for the right to use the item (though a
+ Distribution Fee may be charged).
+
+ (b) recipients of the item may redistribute it under the same
+ conditions they received it.
+
+ (c) If the item is a binary, object code, bytecode, the complete
+ corresponding machine-readable source code is included with the
+ item.
+
+
+ Permission for Use and Modification Without Redistribution
+
+ (1) You are permitted to use the Standard Version and create and use
+ Modified Versions for any purpose without restriction, provided that
+ you do not redistribute the Modified Version to others outside of your
+ company or organization.
+
+
+ Permissions for Redistribution of the Standard Version
+
+ (2) You may make available verbatim copies of the source code of the
+ Standard Version of this Package in any medium without restriction,
+ either gratis or for a Distribution Fee, provided that you duplicate
+ all of the original copyright notices and associated disclaimers. At
+ Your discretion, such verbatim copies may or may not include compiled
+ bytecode, object code or binary versions of the corresponding source
+ code in the same medium.
+
+ (3) You may apply any bug fixes, portability changes, and other
+ modifications made available from any of the Originator(s). The
+ resulting modified Package will still be considered the Standard
+ Version, and may be copied, modified and redistributed under the terms
+ of the original license of the Standard Version as if it were the
+ Standard Version.
+
+
+ Permissions for Redistribution of Modified Versions of the Package as Source
+
+ (4) You may modify your copy of the source code of this Package in any way
+ and distribute that Modified Version (either gratis or for a
+ Distribution Fee, and with or without a corresponding binary, bytecode
+ or object code version of the Modified Version) provided that You
+ clearly indicate what changes You made to the Package, and provided
+ that You do at least ONE of the following:
+
+ (a) make the Modified Version available to the Originator(s) of the
+ Standard Version, under the exact license of the Standard
+ Version, so that the Originator(s) may include your modifications
+ into the Standard Version (at their discretion).
+
+ (b) modify any installation scripts and procedures so that
+ installation of the Modified Version will never conflict with an
+ installation of the Standard Version, include for each program
+ installed by the Modified Version clear documentation describing
+ how it differs from the Standard Version, and rename your
+ Modified Version so that the name is substantially different from
+ the Standard Version.
+
+ (c) permit and encourage anyone who receives a copy of the Modified
+ Version permission to make your modifications Freely Available in
+ some specific way.
+
+ If Your Modified Version is in turn derived from a Modified Version
+ made by a third party, then You are still required to ensure that Your
+ Modified Version complies with the requirements of this license.
+
+
+ Permissions for Redistribution of Non-Source Versions of Package
+
+ (5) You may distribute binary, object code, bytecode or other non-source
+ versions of the Standard Version of the Package, provided that you
+ include complete instructions on where to get the source code of the
+ Standard Version. Such instructions must be valid at the time of Your
+ distribution. If these instructions, at any time while You are
+ carrying our such distribution, become invalid, you must provide new
+ instructions on demand or cease further distribution. If You cease
+ distribution within thirty days after You become aware that the
+ instructions are invalid, then You do not forfeit any of Your rights
+ under this license.
+
+ (6) You may distribute binary, object code, bytecode or other non-source
+ versions of a Modified Version provided that You do at least ONE of
+ the following:
+
+ (a) include a copy of the corresponding source code for the Modified
+ Version under the terms indicated in (4).
+
+ (b) ensure that the installation of Your non-source Modified Version
+ does not conflict in any way with an installation of the Standard
+ Version, include for each program installed by the Modified
+ Version clear documentation describing how it differs from the
+ Standard Version, and rename your Modified Version so that the
+ name is substantially different from the Standard Version.
+
+ (c) ensure that the Modified Version includes notification of the
+ changes made from the Standard Version, and offer to provide
+ machine-readable source code (under a license that permits making
+ that source code Freely Available) of the Modified Version via
+ mail order.
+
+
+ Permissions for Inclusion of the Package in Aggregate Works
+
+ (7) You may aggregate this Package (either the Standard Version or
+ Modified Version) with other packages and distribute the resulting
+ aggregation provided that You do not charge a licensing fee for the
+ Package. Distribution Fees are permitted, and licensing fees for
+ other packages in the aggregation are permitted. Your permission to
+ distribute Standard or Modified Versions of the Package is still
+ subject to the other terms set forth in other sections of this
+ license.
+
+ (8) In addition to the permissions given elsewhere by this license, You
+ are also permitted to link Modified and Standard Versions of this
+ Package with other works and distribute the result without
+ restriction, provided You have produced binary program(s) that do not
+ overtly expose the interfaces of the Package. This includes
+ permission to embed the Package in a larger work of your own without
+ exposing a direct interface to the Package. This also includes
+ permission to build stand-alone binary or bytecode versions of your
+ scripts that require the Package, but do not otherwise give the casual
+ user direct access to the Package itself.
+
+
+ Items That are Never Considered Part of a Modified Version Package
+
+ (9) Works (including, but not limited to, subroutines and scripts) that
+ you have linked or aggregated with the Package that merely extend or
+ make use of the Package, but are not intended to cause the Package to
+ operate differently from the Standard Version, do not, by themselves,
+ cause the Package to be a Modified Version. In addition, such works
+ are not considered parts of the Package itself, and are not bound by
+ the terms of the Package's license.
+
+
+ Acceptance of License and Disclaimer of Warranty
+
+ (10) You are not required to accept this License, since you have not signed
+ it. However, nothing else grants you permission to copy, modify or
+ distribute the Standard or Modified Versions of the Package. These
+ actions are prohibited by copyright law if you do not accept this
+ License. Therefore, by copying, modifying or distributing Standard
+ and Modified Versions of the Package, you indicate your acceptance of
+ the license of the Package.
+
+
+ (11) Disclaimer of Warranty:
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT UNLESS REQUIRED BY
+ LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER OR CONTRIBUTOR
+ BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, PROFITS; OR
+ BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
diff --git a/unrpyc/renpy/README b/unrpyc/renpy/README
new file mode 100644
index 0000000..0e04cdc
--- /dev/null
+++ b/unrpyc/renpy/README
@@ -0,0 +1 @@
+This is Ren'Py 6.15.4 with significant deletions (to minimize size).
diff --git a/unrpyc/renpy/__init__.py b/unrpyc/renpy/__init__.py
new file mode 100644
index 0000000..443cb49
--- /dev/null
+++ b/unrpyc/renpy/__init__.py
@@ -0,0 +1,278 @@
+# 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 ensures that renpy packages will be imported in the right
+# order.
+
+import sys
+import os
+
+# Version numbers.
+try:
+ from renpy.vc_version import vc_version; vc_version
+except ImportError:
+ vc_version = 0
+
+# The tuple giving the version. This needs to be updated when
+# we bump the version.
+#
+# Be sure to change script_version in launcher/script_version.rpy.
+# Be sure to change config.version in tutorial/game/options.rpy.
+version_tuple = (6, 15, 4, vc_version)
+
+# A verbose string computed from that version.
+version = "Ren'Py " + ".".join(str(i) for i in version_tuple)
+
+# Other versions.
+script_version = 5003000
+savegame_suffix = "-LT1.save"
+bytecode_version = 1
+
+# True if this is the first time we've started - even including
+# utter restarts.
+first_utter_start = True
+
+def setup_modulefinder(modulefinder):
+ import _renpy
+
+ libexec = os.path.dirname(_renpy.__file__)
+
+ for i in [ "display", "gl", "angle", "text" ]:
+
+ displaypath = os.path.join(libexec, "renpy", i)
+
+ if os.path.exists(displaypath):
+ modulefinder.AddPackagePath('renpy.' + i, displaypath)
+
+def import_cython():
+ """
+ Never called, but necessary to ensure that modulefinder will properly
+ grab the various cython modules.
+ """
+
+ import renpy.arguments #@UnresolvedImport
+
+ import renpy.display.accelerator #@UnresolvedImport
+ import renpy.display.render #@UnresolvedImport
+
+ import renpy.gl.gldraw #@UnresolvedImport
+ import renpy.gl.glenviron_fixed #@UnresolvedImport
+ import renpy.gl.glenviron_limited #@UnresolvedImport
+ import renpy.gl.glenviron_shader #@UnresolvedImport
+ import renpy.gl.glrtt_copy #@UnresolvedImport
+ import renpy.gl.glrtt_fbo #@UnresolvedImport
+ import renpy.gl.gltexture #@UnresolvedImport
+
+ import renpy.angle.gldraw #@UnresolvedImport
+ import renpy.angle.glenviron_shader #@UnresolvedImport
+ import renpy.angle.glrtt_copy #@UnresolvedImport
+ import renpy.angle.glrtt_fbo #@UnresolvedImport
+ import renpy.angle.gltexture #@UnresolvedImport
+
+
+def import_all():
+
+ import renpy.log #@UnresolvedImport
+
+ import renpy.display #@UnresolvedImport
+
+ # Should probably be early, as we will add it as a base to serialized things.
+ import renpy.object #@UnresolvedImport
+
+ import renpy.game #@UnresolvedImport
+
+ # Adds in the Ren'Py loader.
+ import renpy.loader #@UnresolvedImport
+
+ import renpy.ast #@UnresolvedImport
+ import renpy.atl #@UnresolvedImport
+ import renpy.curry #@UnresolvedImport
+ import renpy.easy #@UnresolvedImport
+ import renpy.execution #@UnresolvedImport
+ import renpy.loadsave #@UnresolvedImport
+ import renpy.parser #@UnresolvedImport
+ import renpy.python #@UnresolvedImport
+ import renpy.script #@UnresolvedImport
+ import renpy.statements #@UnresolvedImport
+ import renpy.style #@UnresolvedImport
+ import renpy.substitutions #@UnresolvedImport
+ import renpy.translation #@UnresolvedImport
+
+ import renpy.display.presplash #@UnresolvedImport
+ import renpy.display.pgrender #@UnresolvedImport
+ import renpy.display.scale #@UnresolvedImport
+ import renpy.display.module #@UnresolvedImport
+
+ def update_path(package):
+ """
+ Update the __path__ of package, to import binary modules from a libexec
+ directory.
+ """
+
+ name = package.__name__.split(".")
+
+ import _renpy #@UnresolvedImport
+ libexec = os.path.dirname(_renpy.__file__)
+ package.__path__.append(os.path.join(libexec, *name))
+
+ # Also find encodings, to deal with the way py2exe lays things out.
+ import encodings
+ libexec = os.path.dirname(encodings.__path__[0])
+ package.__path__.append(os.path.join(libexec, *name))
+
+ update_path(renpy.display)
+
+ import renpy.display.render # Most display stuff depends on this. @UnresolvedImport
+ import renpy.display.core # object @UnresolvedImport
+
+ import renpy.text #@UnresolvedImport
+ update_path(renpy.text)
+
+ import renpy.text.ftfont #@UnresolvedImport
+ import renpy.text.font #@UnresolvedImport
+ import renpy.text.textsupport #@UnresolvedImport
+ import renpy.text.texwrap #@UnresolvedImport
+ import renpy.text.text #@UnresolvedImport
+ import renpy.text.extras #@UnresolvedImport
+
+ sys.modules['renpy.display.text'] = renpy.text.text
+
+ import renpy.gl #@UnresolvedImport
+ update_path(renpy.gl)
+
+ import renpy.angle #@UnresolvedImport
+ update_path(renpy.angle)
+
+ import renpy.display.layout # core @UnresolvedImport
+ import renpy.display.motion # layout @UnresolvedImport
+ import renpy.display.behavior # layout @UnresolvedImport
+ import renpy.display.transition # core, layout @UnresolvedImport
+ import renpy.display.movetransition # core @UnresolvedImport
+ import renpy.display.im #@UnresolvedImport
+ import renpy.display.imagelike #@UnresolvedImport
+ import renpy.display.image # core, behavior, im, imagelike @UnresolvedImport
+ import renpy.display.video #@UnresolvedImport
+ import renpy.display.focus #@UnresolvedImport
+ import renpy.display.anim #@UnresolvedImport
+ import renpy.display.particle #@UnresolvedImport
+ import renpy.display.joystick #@UnresolvedImport
+ import renpy.display.minigame #@UnresolvedImport
+ import renpy.display.screen #@UnresolvedImport
+ import renpy.display.dragdrop #@UnresolvedImport
+ import renpy.display.imagemap #@UnresolvedImport
+ import renpy.display.predict #@UnresolvedImport
+
+ import renpy.display.error #@UnresolvedImport
+
+ # Note: For windows to work, renpy.audio.audio needs to be after
+ # renpy.display.module.
+ import renpy.audio.audio #@UnresolvedImport
+ import renpy.audio.music #@UnresolvedImport
+ import renpy.audio.sound #@UnresolvedImport
+
+ import renpy.ui #@UnresolvedImport
+ import renpy.screenlang #@UnresolvedImport
+
+ import renpy.lint #@UnresolvedImport
+ import renpy.warp #@UnresolvedImport
+
+ import renpy.editor #@UnresolvedImport
+ import renpy.exports #@UnresolvedImport
+ import renpy.character # depends on exports. @UnresolvedImport
+
+ import renpy.dump #@UnresolvedImport
+
+ import renpy.config # depends on lots. @UnresolvedImport
+ import renpy.minstore # depends on lots. @UnresolvedImport
+ import renpy.defaultstore # depends on everything. @UnresolvedImport
+ import renpy.main #@UnresolvedImport
+
+ # Create the store.
+ renpy.python.create_store("store")
+
+ # Import the contents of renpy.defaultstore into renpy.store, and set
+ # up an alias as we do.
+ renpy.store = sys.modules['store']
+ sys.modules['renpy.store'] = sys.modules['store']
+
+ import subprocess
+ sys.modules['renpy.subprocess'] = subprocess
+
+ for k, v in renpy.defaultstore.__dict__.items():
+ renpy.store.__dict__.setdefault(k, v)
+
+ # Import everything into renpy.exports, provided it isn't
+ # already there.
+ for k, v in globals().items():
+ vars(renpy.exports).setdefault(k, v)
+
+# Fool the analyzer.
+if False:
+ import renpy.defaultstore as store
+
+# This reloads all modules.
+def reload_all():
+
+ import renpy #@UnresolvedImport
+
+ # Shut down the cache thread.
+ renpy.display.im.cache.quit()
+
+ blacklist = [ "renpy",
+ "renpy.log",
+ "renpy.bootstrap",
+ "renpy.display",
+ "renpy.display.pgrender",
+ "renpy.display.scale" ]
+
+ for i in list(sys.modules.keys()):
+ if i.startswith("renpy") and i not in blacklist:
+ del sys.modules[i]
+
+ if i.startswith("store"):
+ del sys.modules[i]
+
+ import gc
+ gc.collect()
+
+ renpy.display.draw = None
+
+ import_all()
+
+# Information about the platform we're running on. We break the platforms
+# up into 4 groups - windows-like, mac-like, linux-like, and android-like.
+windows = False
+macintosh = False
+linux = False
+android = False
+
+import platform
+
+if platform.win32_ver()[0]:
+ windows = True
+elif platform.mac_ver()[0]:
+ macintosh = True
+else:
+ linux = True
+
+# The android init code in renpy.py will set linux=False and android=True.
+
+
diff --git a/unrpyc/renpy/ast.py b/unrpyc/renpy/ast.py
new file mode 100644
index 0000000..5bfdb25
--- /dev/null
+++ b/unrpyc/renpy/ast.py
@@ -0,0 +1,1757 @@
+# 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 the AST for the Ren'Py script language. Each class
+# here corresponds to a statement in the script language.
+
+# NOTE:
+# When updating this file, consider if lint.py or warp.py also need
+# updating.
+
+import renpy.display
+
+import re
+import time
+import hashlib
+import collections
+
+def next_node(n):
+ """
+ Indicates the next node that should be executed. When a statement
+ can crash, this should be set as early as possible, so that ignore
+ can bring us there.
+ """
+
+ renpy.game.context().next_node = n
+
+class ParameterInfo(object):
+ """
+ This class is used to store information about parameters to a
+ label.
+ """
+ def __init__(self, parameters, positional, extrapos, extrakw):
+
+ # A list of parameter name, default value pairs.
+ self.parameters = parameters
+
+ # A list, giving the positional parameters to this function,
+ # in order.
+ self.positional = positional
+
+ # A variable that takes the extra positional arguments, if
+ # any. None if no such variable exists.
+ self.extrapos = extrapos
+
+ # A variable that takes the extra keyword arguments, if
+ # any. None if no such variable exists.
+ self.extrakw = extrakw
+
+class ArgumentInfo(object):
+
+ def __init__(self, arguments, extrapos, extrakw):
+
+ # A list of (keyword, expression) pairs. If an argument doesn't
+ # have a keyword, it's thought of as positional.
+ self.arguments = arguments
+
+ # An expression giving extra positional arguments being
+ # supplied to this function.
+ self.extrapos = extrapos
+
+ # An expression giving extra keyword arguments that need
+ # to be supplied to this function.
+ self.extrakw = extrakw
+
+
+def __newobj__(cls, *args):
+ return cls.__new__(cls, *args)
+
+# This represents a string containing python code.
+class PyExpr(str):
+
+ __slots__ = [
+ 'filename',
+ 'linenumber',
+ ]
+
+ def __new__(cls, s, filename, linenumber):
+ self = str.__new__(cls, s)
+ self.filename = filename
+ self.linenumber = linenumber
+ return self
+
+ def __getnewargs__(self):
+ return (str(self), self.filename, self.linenumber) # E1101
+
+class PyCode(object):
+
+ __slots__ = [
+ 'source',
+ 'location',
+ 'mode',
+ 'bytecode',
+ 'hash',
+ ]
+
+ def __getstate__(self):
+ return (1, self.source, self.location, self.mode)
+
+ def __setstate__(self, state):
+ (_, self.source, self.location, self.mode) = state
+ self.bytecode = None
+
+ if renpy.game.script.record_pycode:
+ renpy.game.script.all_pycode.append(self)
+
+ def __init__(self, source, loc=('<none>', 1), mode='exec'):
+
+ if isinstance(source, PyExpr):
+ loc = (source.filename, source.linenumber, source)
+
+ # The source code.
+ self.source = source
+
+ # The time is necessary so we can disambiguate between Python
+ # blocks on the same line in different script versions.
+ self.location = loc + ( int(time.time()), )
+ self.mode = mode
+
+ # This will be initialized later on, after we are serialized.
+ self.bytecode = None
+
+ if renpy.game.script.record_pycode:
+ renpy.game.script.all_pycode.append(self)
+
+ self.hash = None
+
+ def get_hash(self):
+ try:
+ if self.hash is not None:
+ return self.hash
+ except:
+ pass
+
+ code = self.source
+ if isinstance(code, renpy.python.ast.AST): #@UndefinedVariable
+ code = renpy.python.ast.dump(code) #@UndefinedVariable
+
+ self.hash = chr(renpy.bytecode_version) + hashlib.md5(repr(self.location) + code.encode("utf-8")).digest()
+ return self.hash
+
+
+def chain_block(block, next): #@ReservedAssignment
+ """
+ This is called to chain together all of the nodes in a block. Node
+ n is chained with node n+1, while the last node is chained with
+ next.
+ """
+
+ if not block:
+ return
+
+ for a, b in zip(block, block[1:]):
+ a.chain(b)
+
+ block[-1].chain(next)
+
+
+class Scry(object):
+ """
+ This is used to store information about the future, if we know it. Unlike
+ predict, this tries to only get things we _know_ will happen.
+ """
+
+ # By default, all attributes are None.
+ def __getattr__(self, name):
+ return None
+
+ def __next__(self): #@ReservedAssignment
+ if self._next is None:
+ return None
+ else:
+ return self._next.scry()
+
+
+class Node(object):
+ """
+ A node in the abstract syntax tree of the program.
+
+ @ivar name: The name of this node.
+
+ @ivar filename: The filename where this node comes from.
+ @ivar linenumber: The line number of the line on which this node is defined.
+ """
+
+ __slots__ = [
+ 'name',
+ 'filename',
+ 'linenumber',
+ 'next',
+ ]
+
+ # True if this node is translatable, false otherwise. (This can be set on
+ # the class or the instance.)
+ translatable = False
+
+ # Called to set the state of a Node, when necessary.
+ def __setstate__(self, state):
+ for k, v in state[1].items():
+ try:
+ setattr(self, k, v)
+ except AttributeError:
+ pass
+
+
+ def __init__(self, loc):
+ """
+ Initializes this Node object.
+
+ @param loc: A (filename, physical line number) tuple giving the
+ logical line on which this Node node starts.
+ """
+
+ self.filename, self.linenumber = loc
+ self.name = None
+ self.next = None
+
+ def diff_info(self):
+ """
+ Returns a tuple of diff info about ourself. This is used to
+ compare Nodes to see if they should be considered the same node. The
+ tuple returned must be hashable.
+ """
+
+ return ( id(self), )
+
+ def get_children(self):
+ """
+ Returns a list of all of the nodes that are children of this
+ node. (That is, all of the nodes in any block associated with
+ this node.)
+ """
+
+ return [ ]
+
+ def get_init(self):
+ """
+ Returns a node that should be run at init time (that is, before
+ the normal start of the script.), or None if this node doesn't
+ care to suggest one.
+
+ (The only class that needs to override this is Init.)
+ """
+
+ return None
+
+ def chain(self, next): #@ReservedAssignment
+ """
+ This is called with the Node node that should be followed after
+ executing this node, and all nodes that this node
+ executes. (For example, if this node is a block label, the
+ next is the node that should be executed after all nodes in
+ the block.)
+ """
+
+ self.next = next
+
+ def execute(self):
+ """
+ Causes this node to execute, and any action it entails to be
+ performed. The node should call next_node with the node to
+ be executed after this one.
+ """
+
+ assert False, "Node subclass forgot to define execute."
+
+ def early_execute(self):
+ """
+ Called when the module is loaded.
+ """
+
+ def predict(self):
+ """
+ This is called to predictively load images from this node. It
+ should cause renpy.display.predict.image and
+ renpy.display.predict.screen to be called as necessary.
+ """
+
+ if self.__next__:
+ return [ self.__next__ ]
+ else:
+ return [ ]
+
+ def scry(self):
+ """
+ Called to return an object with some general, user-definable information
+ about the future.
+ """
+
+ rv = Scry()
+ rv._next = self.__next__ # W0201
+ return rv
+
+ def restructure(self, callback):
+ """
+ Called to restructure the AST.
+
+ When this method is called, callback is called once for each child
+ block of the node. The block, a list, can be updated by the callback
+ using slice assignment to the list.
+ """
+
+ # Does nothing for nodes that do not contain child blocks.
+ return
+
+ def get_code(self, dialogue_filter=None):
+ """
+ Returns the canonical form of the code corresponding to this statement.
+ This only needs to be defined if the statement is translatable.
+
+ `filter`
+ If present, a filter that should be applied to human-readable
+ text in the statement.
+ """
+
+ raise Exception("Not Implemented")
+
+def say_menu_with(expression, callback):
+ """
+ This handles the with clause of a say or menu statement.
+ """
+
+ if expression is not None:
+ what = renpy.python.py_eval(expression)
+ elif renpy.store.default_transition and renpy.game.preferences.transitions == 2:
+ what = renpy.store.default_transition
+ else:
+ return
+
+ if not what:
+ return
+
+ if renpy.game.preferences.transitions:
+ # renpy.game.interface.set_transition(what)
+ callback(what)
+
+class Say(Node):
+
+ __slots__ = [
+ 'who',
+ 'who_fast',
+ 'what',
+ 'with_',
+ 'interact',
+ 'attributes',
+ ]
+
+ def diff_info(self):
+ return (Say, self.who, self.what)
+
+ def __setstate__(self, state):
+ self.attributes = None
+ self.interact = True
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, who, what, with_, interact=True, attributes=None):
+
+ super(Say, self).__init__(loc)
+
+ if who is not None:
+ self.who = who.strip()
+
+ if re.match(r'[a-zA-Z_]\w*$', self.who):
+ self.who_fast = True
+ else:
+ self.who_fast = False
+ else:
+ self.who = None
+ self.who_fast = False
+
+ self.what = what
+ self.with_ = with_
+ self.interact = interact
+
+ # A tuple of attributes that are applied to the character that's
+ # speaking, or None to disable this behavior.
+ self.attributes = attributes
+
+ def get_code(self, dialogue_filter=None):
+ rv = [ ]
+
+ if self.who:
+ rv.append(self.who)
+
+ if self.attributes is not None:
+ rv.extend(self.attributes)
+
+ what = self.what
+ if dialogue_filter is not None:
+ what = dialogue_filter(what)
+
+ rv.append(renpy.translation.encode_say_string(what))
+
+ if not self.interact:
+ rv.append("nointeract")
+
+ if self.with_:
+ rv.append("with")
+ rv.append(self.with_)
+
+ return " ".join(rv)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ try:
+
+ renpy.exports.say_attributes = self.attributes
+
+ if self.who is not None:
+ if self.who_fast:
+ who = getattr(renpy.store, self.who, None)
+ if who is None:
+ raise Exception("Sayer '%s' is not defined." % self.who.encode("utf-8"))
+ else:
+ who = renpy.python.py_eval(self.who)
+ else:
+ who = None
+
+ if not (
+ (who is None) or
+ isinstance(who, collections.Callable) or
+ isinstance(who, str) ):
+
+ raise Exception("Sayer %s is not a function or string." % self.who.encode("utf-8"))
+
+ what = self.what
+ if renpy.config.say_menu_text_filter:
+ what = renpy.config.say_menu_text_filter(what) # E1102
+
+ if getattr(who, "record_say", True):
+ renpy.store._last_say_who = self.who
+ renpy.store._last_say_what = what
+
+ say_menu_with(self.with_, renpy.game.interface.set_transition)
+ renpy.exports.say(who, what, interact=self.interact)
+
+ finally:
+ renpy.exports.say_attributes = None
+
+
+ def predict(self):
+
+ old_attributes = renpy.exports.say_attributes
+
+ try:
+
+ renpy.exports.say_attributes = self.attributes
+
+ if self.who is not None:
+ if self.who_fast:
+ who = getattr(renpy.store, self.who)
+ else:
+ who = renpy.python.py_eval(self.who)
+ else:
+ who = None
+
+ def predict_with(trans):
+ renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+
+ say_menu_with(self.with_, predict_with)
+
+ what = self.what
+ if renpy.config.say_menu_text_filter:
+ what = renpy.config.say_menu_text_filter(what)
+
+ renpy.exports.predict_say(who, what)
+
+ finally:
+ renpy.exports.say_attributes = old_attributes
+
+ return [ self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+
+ if self.who is not None:
+ if self.who_fast:
+ who = getattr(renpy.store, self.who)
+ else:
+ who = renpy.python.py_eval(self.who)
+ else:
+ who = None
+
+ if self.interact:
+ renpy.exports.scry_say(who, rv)
+ else:
+ rv.interacts = False
+
+ return rv
+
+# Copy the descriptor.
+setattr(Say, "with", Say.with_) # E1101
+
+class Init(Node):
+
+ __slots__ = [
+ 'block',
+ 'priority',
+ ]
+
+ def __init__(self, loc, block, priority):
+ super(Init, self).__init__(loc)
+
+ self.block = block
+ self.priority = priority
+
+
+ def get_children(self):
+ return self.block
+
+ def get_init(self):
+ return self.priority, self.block[0]
+
+ # We handle chaining specially. We want to chain together the nodes in
+ # the block, but we want that chain to end in None, and we also want
+ # this node to just continue on to the next node in normal execution.
+ def chain(self, next): #@ReservedAssignment
+ self.next = next
+
+ chain_block(self.block, None)
+
+ def execute(self):
+ next_node(self.__next__)
+
+ def restructure(self, callback):
+ callback(self.block)
+
+def apply_arguments(params, args, kwargs):
+ """
+ Applies arguments to parameters to update scope.
+
+ `scope`
+ A dict.
+
+ `params`
+ The parameters object.
+
+ `args`, `kwargs`
+ Positional and keyword arguments.
+ """
+
+ values = { }
+ rv = { }
+
+ if args is None:
+ args = ()
+
+ if kwargs is None:
+ kwargs = { }
+
+ if params is None:
+ if args or kwargs:
+ raise Exception("Arguments supplied, but parameter list not present")
+ else:
+ return rv
+
+ for name, value in zip(params.positional, args):
+ if name in values:
+ raise Exception("Parameter %s has two values." % name)
+
+ values[name] = value
+
+ extrapos = tuple(args[len(params.positional):])
+
+ for name, value in kwargs.items():
+ if name in values:
+ raise Exception("Parameter %s has two values." % name)
+
+ values[name] = value
+
+ for name, default in params.parameters:
+
+ if name not in values:
+ if default is None:
+ raise Exception("Required parameter %s has no value." % name)
+ else:
+ rv[name] = renpy.python.py_eval(default)
+
+ else:
+ rv[name] = values[name]
+ del values[name]
+
+ # Now, values has the left-over keyword arguments, and extrapos
+ # has the left-over positional arguments.
+
+ if params.extrapos:
+ rv[params.extrapos] = extrapos
+ elif extrapos:
+ raise Exception("Too many arguments in call (expected %d, got %d)." % (len(params.positional), len(args)))
+
+ if params.extrakw:
+ rv[params.extrakw] = values
+ else:
+ if values:
+ raise Exception("Unknown keyword arguments: %s" % ( ", ".join(list(values.keys()))))
+
+ return rv
+
+class Label(Node):
+
+ __slots__ = [
+ 'name',
+ 'parameters',
+ 'block',
+ 'hide',
+ ]
+
+ def __setstate__(self, state):
+ self.parameters = None
+ self.hide = False
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, name, block, parameters, hide=False):
+ """
+ Constructs a new Label node.
+
+ @param name: The name of this label.
+ @param block: A (potentially empty) list of nodes making up the
+ block associated with this label.
+ """
+
+ super(Label, self).__init__(loc)
+
+ self.name = name
+ self.block = block
+ self.parameters = parameters
+ self.hide = hide
+
+ def diff_info(self):
+ return (Label, self.name)
+
+ def get_children(self):
+ return self.block
+
+ def chain(self, next): #@ReservedAssignment
+
+ if self.block:
+ self.next = self.block[0]
+ chain_block(self.block, next)
+ else:
+ self.next = next
+
+ def execute(self):
+ next_node(self.__next__)
+
+ renpy.game.context().mark_seen()
+
+ values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
+
+ for k, v in values.items():
+ renpy.exports.dynamic(k)
+ setattr(renpy.store, k, v)
+
+ renpy.store._args = None
+ renpy.store._kwargs = None
+
+ if renpy.config.label_callback:
+ renpy.config.label_callback(self.name, renpy.game.context().last_abnormal)
+
+ def restructure(self, callback):
+ callback(self.block)
+
+
+class Python(Node):
+
+ __slots__ = [
+ 'hide',
+ 'code',
+ 'store',
+ ]
+
+ def __setstate__(self, state):
+ self.store = "store"
+ super(Python, self).__setstate__(state)
+
+ def __init__(self, loc, python_code, hide=False, store="store"):
+ """
+ @param code: A PyCode object.
+
+ @param hide: If True, the code will be executed with its
+ own local dictionary.
+ """
+
+ super(Python, self).__init__(loc)
+
+ self.hide = hide
+ self.code = PyCode(python_code, loc=loc, mode='exec')
+ self.store = store
+
+ def diff_info(self):
+ return (Python, self.code.source)
+
+ def early_execute(self):
+ renpy.python.create_store(self.store)
+
+ def execute(self):
+ next_node(self.__next__)
+
+ try:
+ renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+ finally:
+
+ if not renpy.game.context().init_phase:
+ for i in renpy.config.python_callbacks:
+ i()
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv.interacts = True
+ return rv
+
+class EarlyPython(Node):
+
+ __slots__ = [
+ 'hide',
+ 'code',
+ 'store',
+ ]
+
+ def __setstate__(self, state):
+ self.store = "store"
+ super(EarlyPython, self).__setstate__(state)
+
+ def __init__(self, loc, python_code, hide=False, store="store"):
+ """
+ @param code: A PyCode object.
+
+ @param hide: If True, the code will be executed with its
+ own local dictionary.
+ """
+
+ super(EarlyPython, self).__init__(loc)
+
+ self.hide = hide
+ self.code = PyCode(python_code, loc=loc, mode='exec')
+ self.store = store
+
+ def diff_info(self):
+ return (EarlyPython, self.code.source)
+
+ def execute(self):
+ next_node(self.__next__)
+
+ def early_execute(self):
+ renpy.python.create_store(self.store)
+
+ if self.code.bytecode:
+ renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+
+class Image(Node):
+
+ __slots__ = [
+ 'imgname',
+ 'code',
+ 'atl',
+ ]
+
+ def __init__(self, loc, name, expr=None, atl=None):
+ """
+ @param name: The name of the image being defined.
+
+ @param expr: An expression yielding a Displayable that is
+ assigned to the image.
+ """
+
+ super(Image, self).__init__(loc)
+
+ self.imgname = name
+
+ if expr:
+ self.code = PyCode(expr, loc=loc, mode='eval')
+ self.atl = None
+ else:
+ self.code = None
+ self.atl = atl
+
+ def diff_info(self):
+ return (Image, tuple(self.imgname))
+
+ def execute(self):
+
+ # Note: We should always check that self.code is None before
+ # accessing self.atl, as self.atl may not always exist.
+
+ next_node(self.__next__)
+
+ if self.code is not None:
+ img = renpy.python.py_eval_bytecode(self.code.bytecode)
+ else:
+ img = renpy.display.motion.ATLTransform(self.atl)
+
+ renpy.exports.image(self.imgname, img)
+
+
+
+class Transform(Node):
+
+ __slots__ = [
+
+ # The name of the transform.
+ 'varname',
+
+ # The block of ATL associated with the transform.
+ 'atl',
+
+ # The parameters associated with the transform, if any.
+ 'parameters',
+ ]
+
+ default_parameters = ParameterInfo([ ], [ ], None, None)
+
+ def __init__(self, loc, name, atl=None, parameters=default_parameters):
+
+ super(Transform, self).__init__(loc)
+
+ self.varname = name
+ self.atl = atl
+ self.parameters = parameters
+
+ def diff_info(self):
+ return (Transform, self.varname)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ parameters = getattr(self, "parameters", None)
+
+ if parameters is None:
+ parameters = Transform.default_parameters
+
+ trans = renpy.display.motion.ATLTransform(self.atl, parameters=parameters)
+ renpy.dump.transforms.append((self.varname, self.filename, self.linenumber))
+ setattr(renpy.store, self.varname, trans)
+
+
+def predict_imspec(imspec, scene=False, atl=None):
+ """
+ Call this to use the given callback to predict the image named
+ in imspec.
+ """
+
+ if len(imspec) == 7:
+ name, expression, tag, _at_list, layer, _zorder, _behind = imspec
+
+ elif len(imspec) == 6:
+ name, expression, tag, _at_list, layer, _zorder = imspec
+
+ elif len(imspec) == 3:
+ name, _at_list, layer = imspec
+
+
+ if expression:
+ try:
+ img = renpy.python.py_eval(expression)
+ img = renpy.easy.displayable(img)
+ except:
+ return
+
+ else:
+ img = renpy.display.image.images.get(name, None)
+ if img is None:
+ return
+
+ full_name = name
+ if tag:
+ full_name = (tag,) + full_name[1:]
+
+ if scene:
+ renpy.game.context().images.predict_scene(layer)
+
+ renpy.game.context().images.predict_show(tag or name, layer)
+
+ if atl is not None:
+ try:
+ img = renpy.display.motion.ATLTransform(atl, child=img)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ renpy.display.predict.displayable(img)
+
+
+
+def show_imspec(imspec, atl=None):
+
+ if len(imspec) == 7:
+ name, expression, tag, at_list, layer, zorder, behind = imspec
+
+ elif len(imspec) == 6:
+ name, expression, tag, at_list, layer, zorder = imspec
+ behind = [ ]
+
+ elif len(imspec) == 3:
+ name, at_list, layer = imspec
+ expression = None
+ tag = None
+ zorder = None
+ behind = [ ]
+
+ if zorder is not None:
+ zorder = renpy.python.py_eval(zorder)
+ else:
+ zorder = 0
+
+ if expression is not None:
+ expression = renpy.python.py_eval(expression)
+ expression = renpy.easy.displayable(expression)
+
+ at_list = [ renpy.python.py_eval(i) for i in at_list ]
+
+ renpy.config.show(name,
+ at_list=at_list,
+ layer=layer,
+ what=expression,
+ zorder=zorder,
+ tag=tag,
+ behind=behind,
+ atl=atl)
+
+class Show(Node):
+
+ __slots__ = [
+ 'imspec',
+ 'atl',
+ ]
+
+ def __init__(self, loc, imspec, atl=None):
+ """
+ @param imspec: A triple consisting of an image name (itself a
+ tuple of strings), a list of at expressions, and a layer.
+ """
+
+ super(Show, self).__init__(loc)
+
+ self.imspec = imspec
+ self.atl = atl
+
+ def diff_info(self):
+ return (Show, tuple(self.imspec[0]))
+
+ def execute(self):
+ next_node(self.__next__)
+
+ show_imspec(self.imspec, atl=getattr(self, "atl", None))
+
+ def predict(self):
+ predict_imspec(self.imspec, atl=getattr(self, "atl", None))
+ return [ self.__next__ ]
+
+
+class Scene(Node):
+
+ __slots__ = [
+ 'imspec',
+ 'layer',
+ 'atl',
+ ]
+
+ def __init__(self, loc, imgspec, layer, atl=None):
+ """
+ @param imspec: A triple consisting of an image name (itself a
+ tuple of strings), a list of at expressions, and a layer, or
+ None to not have this scene statement also display an image.
+ """
+
+ super(Scene, self).__init__(loc)
+
+ self.imspec = imgspec
+ self.layer = layer
+ self.atl = atl
+
+ def diff_info(self):
+
+ if self.imspec:
+ data = tuple(self.imspec[0])
+ else:
+ data = None
+
+ return (Scene, data)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ renpy.config.scene(self.layer)
+
+ if self.imspec:
+ show_imspec(self.imspec, atl=getattr(self, "atl", None))
+
+ def predict(self):
+
+ if self.imspec:
+ predict_imspec(self.imspec, atl=getattr(self, "atl", None), scene=True)
+
+ return [ self.__next__ ]
+
+
+class Hide(Node):
+
+ __slots__ = [
+ 'imspec',
+ ]
+
+ def __init__(self, loc, imgspec):
+ """
+ @param imspec: A triple consisting of an image name (itself a
+ tuple of strings), a list of at expressions, and a list of
+ with expressions.
+ """
+
+ super(Hide, self).__init__(loc)
+
+ self.imspec = imgspec
+
+ def diff_info(self):
+ return (Hide, tuple(self.imspec[0]))
+
+ def predict(self):
+
+ if len(self.imspec) == 3:
+ name, _at_list, layer = self.imspec
+ tag = None
+ _expression = None
+ _zorder = None
+ _behind = None
+ elif len(self.imspec) == 6:
+ name, _expression, tag, _at_list, layer, _zorder = self.imspec
+ _behind = None
+ elif len(self.imspec) == 7:
+ name, _expression, tag, _at_list, layer, _zorder, _behind = self.imspec
+
+
+ if tag is None:
+ tag = name[0]
+
+ renpy.game.context().images.predict_hide(tag, layer)
+
+ return [ self.__next__ ]
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ if len(self.imspec) == 3:
+ name, _at_list, layer = self.imspec
+ _expression = None
+ tag = None
+ _zorder = 0
+ elif len(self.imspec) == 6:
+ name, _expression, tag, _at_list, layer, _zorder = self.imspec
+ elif len(self.imspec) == 7:
+ name, _expression, tag, _at_list, layer, _zorder, _behind = self.imspec
+
+ renpy.config.hide(tag or name, layer)
+
+
+class With(Node):
+
+ __slots__ = [
+ 'expr',
+ 'paired',
+ ]
+
+ def __setstate__(self, state):
+ self.paired = None
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, expr, paired=None):
+ """
+ @param expr: An expression giving a transition or None.
+ """
+
+ super(With, self).__init__(loc)
+ self.expr = expr
+ self.paired = paired
+
+ def diff_info(self):
+ return (With, self.expr)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ trans = renpy.python.py_eval(self.expr)
+
+ if self.paired is not None:
+ paired = renpy.python.py_eval(self.paired)
+ else:
+ paired = None
+
+ renpy.exports.with_statement(trans, paired)
+
+ def predict(self):
+
+ try:
+ trans = renpy.python.py_eval(self.expr)
+
+ if trans:
+ renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+
+ except:
+ pass
+
+
+ return [ self.__next__ ]
+
+
+class Call(Node):
+
+ __slots__ = [
+ 'label',
+ 'arguments',
+ 'expression',
+ ]
+
+ def __setstate__(self, state):
+ self.arguments = None
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, label, expression, arguments):
+
+ super(Call, self).__init__(loc)
+ self.label = label
+ self.expression = expression
+ self.arguments = arguments
+
+ def diff_info(self):
+ return (Call, self.label, self.expression)
+
+ def execute(self):
+
+ label = self.label
+ if self.expression:
+ label = renpy.python.py_eval(label)
+
+ rv = renpy.game.context().call(label, return_site=self.next.name)
+ next_node(rv)
+ renpy.game.context().abnormal = True
+
+ if self.arguments:
+
+ args = [ ]
+ kwargs = renpy.python.RevertableDict()
+
+ for name, expr in self.arguments.arguments:
+
+ value = renpy.python.py_eval(expr)
+
+ if name is None:
+ args.append(value)
+ else:
+ if name in kwargs:
+ raise Exception("The argument named %s appears twice." % name)
+
+ kwargs[name] = value
+
+ if self.arguments.extrapos:
+ args.extend(renpy.python.py_eval(self.arguments.extrapos))
+
+ if self.arguments.extrakw:
+ for name, value in renpy.python.py_eval(self.arguments.extrakw).items():
+ if name in kwargs:
+ raise Exception("The argument named %s appears twice." % name)
+
+ kwargs[name] = value
+
+
+ renpy.store._args = tuple(args)
+ renpy.store._kwargs = kwargs
+
+ def predict(self):
+ if self.expression:
+ return [ ]
+ else:
+ return [ renpy.game.script.lookup(self.label) ]
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = None
+ return rv
+
+
+class Return(Node):
+
+ __slots__ = [ 'expression']
+
+ def __setstate__(self, state):
+ self.expression = None
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, expression):
+ super(Return, self).__init__(loc)
+ self.expression = expression
+
+ def diff_info(self):
+ return (Return, )
+
+ # We don't care what the next node is.
+ def chain(self, next): #@ReservedAssignment
+ self.next = None
+ return
+
+ def execute(self):
+
+ if self.expression:
+ renpy.store._return = renpy.python.py_eval(self.expression)
+ else:
+ renpy.store._return = None
+
+ renpy.game.context().pop_dynamic()
+
+ next_node(renpy.game.context().lookup_return(pop=True))
+
+ def predict(self):
+ site = renpy.game.context().lookup_return(pop=False)
+ if site:
+ return [ site ]
+ else:
+ return [ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = None
+ return rv
+
+
+class Menu(Node):
+
+ __slots__ = [
+ 'items',
+ 'set',
+ 'with_',
+ ]
+
+ def __init__(self, loc, items, set, with_): #@ReservedAssignment
+ super(Menu, self).__init__(loc)
+
+ self.items = items
+ self.set = set
+ self.with_ = with_
+
+ def diff_info(self):
+ return (Menu,)
+
+ def get_children(self):
+ rv = [ ]
+
+ for _label, _condition, block in self.items:
+ if block:
+ rv.extend(block)
+
+ return rv
+
+ # Blocks of statements in a choice continue after the menu.
+ def chain(self, next): #@ReservedAssignment
+
+ self.next = next
+
+ for (_label, _condition, block) in self.items:
+ if block:
+ chain_block(block, next)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ choices = [ ]
+ narration = [ ]
+
+ for i, (label, condition, block) in enumerate(self.items):
+
+ if renpy.config.say_menu_text_filter:
+ label = renpy.config.say_menu_text_filter(label)
+
+ if block is None:
+ if renpy.config.narrator_menu and label:
+ narration.append(label)
+ else:
+ choices.append((label, condition, None))
+ else:
+ choices.append((label, condition, i))
+ next_node(block[0])
+
+ if narration:
+ renpy.exports.say(None, "\n".join(narration), interact=False)
+
+ say_menu_with(self.with_, renpy.game.interface.set_transition)
+ choice = renpy.exports.menu(choices, self.set)
+
+ if choice is not None:
+ next_node(self.items[choice][2][0])
+ else:
+ next_node(self.__next__)
+
+
+ def predict(self):
+ rv = [ ]
+
+ def predict_with(trans):
+ renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+
+ say_menu_with(self.with_, predict_with)
+
+ renpy.store.predict_menu()
+
+ for _label, _condition, block in self.items:
+ if block:
+ rv.append(block[0])
+
+ return rv
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = None
+ rv.interacts = True
+ return rv
+
+ def restructure(self, callback):
+ for _label, _condition, block in self.items:
+ if block is not None:
+ callback(block)
+
+setattr(Menu, "with", Menu.with_) # E1101
+
+
+# Goto is considered harmful. So we decided to name it "jump"
+# instead.
+class Jump(Node):
+
+ __slots__ = [
+ 'target',
+ 'expression',
+ ]
+
+ def __init__(self, loc, target, expression):
+ super(Jump, self).__init__(loc)
+
+ self.target = target
+ self.expression = expression
+
+ def diff_info(self):
+ return (Jump, self.target, self.expression)
+
+ # We don't care what our next node is.
+ def chain(self, next): #@ReservedAssignment
+ self.next = None
+ return
+
+ def execute(self):
+
+ target = self.target
+ if self.expression:
+ target = renpy.python.py_eval(target)
+
+ rv = renpy.game.script.lookup(target)
+ renpy.game.context().abnormal = True
+
+ next_node(rv)
+
+ def predict(self):
+
+ if self.expression:
+ return [ ]
+ else:
+ return [ renpy.game.script.lookup(self.target) ]
+
+ def scry(self):
+ rv = Node.scry(self)
+ if self.expression:
+ rv._next = None
+ else:
+ rv._next = renpy.game.script.lookup(self.target)
+
+ return rv
+
+
+# GNDN
+class Pass(Node):
+
+ __slots__ = [ ]
+
+ def diff_info(self):
+ return (Pass,)
+
+ def execute(self):
+ next_node(self.__next__)
+
+
+class While(Node):
+
+ __slots__ = [
+ 'condition',
+ 'block',
+ ]
+
+ def __init__(self, loc, condition, block):
+ super(While, self).__init__(loc)
+
+ self.condition = condition
+ self.block = block
+
+ def diff_info(self):
+ return (While, self.condition)
+
+ def get_children(self):
+ return self.block
+
+ def chain(self, next): #@ReservedAssignment
+ self.next = next
+ chain_block(self.block, self)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ if renpy.python.py_eval(self.condition):
+ next_node(self.block[0])
+
+ def predict(self):
+ return [ self.block[0], self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = None
+ return rv
+
+ def restructure(self, callback):
+ callback(self.block)
+
+class If(Node):
+
+ __slots__ = [ 'entries' ]
+
+ def __init__(self, loc, entries):
+ """
+ @param entries: A list of (condition, block) tuples.
+ """
+
+ super(If, self).__init__(loc)
+
+ self.entries = entries
+
+ def diff_info(self):
+ return (If,)
+
+ def get_children(self):
+ rv = [ ]
+
+ for _condition, block in self.entries:
+ rv.extend(block)
+
+ return rv
+
+ def chain(self, next): #@ReservedAssignment
+ self.next = next
+
+ for _condition, block in self.entries:
+ chain_block(block, next)
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ for condition, block in self.entries:
+ if renpy.python.py_eval(condition):
+ next_node(block[0])
+ return
+
+ def predict(self):
+
+ return [ block[0] for _condition, block in self.entries ] + \
+ [ self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = None
+ return rv
+
+ def restructure(self, callback):
+ for _condition, block in self.entries:
+ callback(block)
+
+class UserStatement(Node):
+
+ __slots__ = [
+ 'line',
+ 'parsed',
+ 'block',
+ 'translatable' ]
+
+ def __setstate__(self, state):
+ self.block = [ ]
+ self.translatable = False
+ Node.__setstate__(self, state)
+
+ def __init__(self, loc, line, block):
+
+ super(UserStatement, self).__init__(loc)
+ self.line = line
+ self.block = block
+ self.parsed = None
+
+ # Do not store the parse quite yet.
+ _parse_info = renpy.statements.parse(self, self.line, self.block)
+
+ def diff_info(self):
+ return (UserStatement, self.line)
+
+ def execute(self):
+ next_node(self.get_next())
+
+ self.call("execute")
+
+ def predict(self):
+ self.call("predict")
+ return [ self.get_next() ]
+
+ def call(self, method, *args, **kwargs):
+
+ parsed = self.parsed
+ if parsed is None:
+ parsed = renpy.statements.parse(self, self.line, self.block)
+ self.parsed = parsed
+
+ renpy.statements.call(method, parsed, *args, **kwargs)
+
+ def get_next(self):
+ rv = self.call("next")
+ if rv is not None:
+ return renpy.game.script.lookup(rv)
+ else:
+ return self.__next__
+
+ def scry(self):
+ rv = Node.scry(self)
+ rv._next = self.get_next()
+ self.call("scry", rv)
+ return rv
+
+ def get_code(self, dialogue_filter=None):
+ return self.line
+
+
+class Define(Node):
+
+ __slots__ = [
+ 'varname',
+ 'code',
+ ]
+
+ def __init__(self, loc, name, expr):
+ """
+ @param name: The name of the image being defined.
+
+ @param expr: An expression yielding a Displayable that is
+ assigned to the image.
+ """
+
+ super(Define, self).__init__(loc)
+
+ self.varname = name
+ self.code = PyCode(expr, loc=loc, mode='eval')
+
+ def diff_info(self):
+ return (Define, tuple(self.varname))
+
+ def execute(self):
+
+ next_node(self.__next__)
+
+ value = renpy.python.py_eval_bytecode(self.code.bytecode)
+ renpy.dump.definitions.append((self.varname, self.filename, self.linenumber))
+ setattr(renpy.store, self.varname, value)
+
+
+class Screen(Node):
+
+ __slots__ = [
+ 'screen',
+ ]
+
+ def __init__(self, loc, screen):
+ """
+ @param name: The name of the image being defined.
+
+ @param expr: An expression yielding a Displayable that is
+ assigned to the image.
+ """
+
+ super(Screen, self).__init__(loc)
+
+ self.screen = screen
+
+ def diff_info(self):
+ return (Screen, self.screen.name)
+
+ def execute(self):
+ next_node(self.__next__)
+ self.screen.define()
+ renpy.dump.screens.append((self.screen.name, self.filename, self.linenumber))
+
+
+################################################################################
+# Translations
+################################################################################
+
+class Translate(Node):
+ """
+ A translation block, produced either by explicit translation statements
+ or implicit translation blocs.
+
+ If language is None, when executed this transfers control to the translate
+ statement in the current language, if any, and otherwise runs the block.
+ If language is not None, causes an error to occur if control reaches this
+ statement.
+
+ When control normally leaves a translate statement, in any language, it
+ goes to the end of the translate statement in the None language.
+ """
+
+ __slots__ = [
+ "identifier",
+ "language",
+ "block",
+ ]
+
+ def __init__(self, loc, identifier, language, block):
+ super(Translate, self).__init__(loc)
+
+ self.identifier = identifier
+ self.language = language
+ self.block = block
+
+ def diff_info(self):
+ return (Translate, self.identifier, self.language)
+
+ def chain(self, next): #@ReservedAssignment
+ self.next = next
+ chain_block(self.block, next)
+
+ def execute(self):
+
+ if self.language is not None:
+ next_node(self.__next__)
+ raise Exception("Translation nodes cannot be run directly.")
+
+ next_node(renpy.game.script.translator.lookup_translate(self.identifier))
+ renpy.game.context().translate_identifier = self.identifier
+
+ def predict(self):
+ node = renpy.game.script.translator.lookup_translate(self.identifier)
+ return [ node ]
+
+ def scry(self):
+ rv = Scry()
+ rv._next = renpy.game.script.translator.lookup_translate(self.identifier)
+ return rv
+
+ def get_children(self):
+ return self.block
+
+ def restructure(self, callback):
+ return callback(self.block)
+
+
+class EndTranslate(Node):
+ """
+ A node added implicitly after each translate block. It's responsible for
+ resetting the translation identifier.
+ """
+
+ def __init__(self, loc):
+ super(EndTranslate, self).__init__(loc)
+
+ def diff_info(self):
+ return (EndTranslate,)
+
+ def execute(self):
+ next_node(self.__next__)
+ renpy.game.context().translate_identifier = None
+
+
+class TranslateString(Node):
+ """
+ A node used for translated strings.
+ """
+
+ __slots__ = [
+ "language",
+ "old",
+ "new"
+ ]
+
+ def __init__(self, loc, language, old, new):
+ super(TranslateString, self).__init__(loc)
+ self.language = language
+ self.old = old
+ self.new = new
+
+ def diff_info(self):
+ return (TranslateString,)
+
+ def execute(self):
+ next_node(self.__next__)
+ renpy.translation.add_string_translation(self.language, self.old, self.new)
+
+class TranslatePython(Node):
+
+ __slots__ = [
+ 'language',
+ 'code',
+ ]
+
+ def __init__(self, loc, language, python_code):
+ """
+ @param code: A PyCode object.
+
+ @param hide: If True, the code will be executed with its
+ own local dictionary.
+ """
+
+ super(TranslatePython, self).__init__(loc)
+
+ self.language = language
+ self.code = PyCode(python_code, loc=loc, mode='exec')
+
+ def diff_info(self):
+ return (TranslatePython, self.code.source)
+
+ def execute(self):
+ next_node(self.__next__)
+
+ # def early_execute(self):
+ # renpy.python.create_store(self.store)
+ # renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+
+
diff --git a/unrpyc/renpy/atl.py b/unrpyc/renpy/atl.py
new file mode 100644
index 0000000..f314e3e
--- /dev/null
+++ b/unrpyc/renpy/atl.py
@@ -0,0 +1,1544 @@
+# 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.
+
+import renpy.display
+import random
+
+def compiling(loc):
+ file, number = loc #@ReservedAssignment
+
+ renpy.game.exception_info = "Compiling ATL code at %s:%d" % (file, number)
+
+def executing(loc):
+ file, number = loc #@ReservedAssignment
+
+ renpy.game.exception_info = "Executing ATL code at %s:%d" % (file, number)
+
+
+# A map from the name of a time warp function to the function itself.
+warpers = { }
+
+def atl_warper(f):
+ name = f.__name__
+ warpers[name] = f
+ return f
+
+# The pause warper is used internally when no other warper is
+# specified.
+@atl_warper
+def pause(t):
+ if t < 1.0:
+ return 0.0
+ else:
+ return 1.0
+
+position = object()
+
+# A dictionary giving property names and the corresponding default
+# values.
+PROPERTIES = {
+ "pos" : (position, position),
+ "xpos" : position,
+ "ypos" : position,
+ "anchor" : (position, position),
+ "xanchor" : position,
+ "yanchor" : position,
+ "xaround" : position,
+ "yaround" : position,
+ "xanchoraround" : float,
+ "yanchoraround" : float,
+ "align" : (float, float),
+ "xalign" : float,
+ "yalign" : float,
+ "rotate" : float,
+ "rotate_pad" : bool,
+ "transform_anchor" : bool,
+ "xzoom" : float,
+ "yzoom" : float,
+ "zoom" : float,
+ "alpha" : float,
+ "around" : (position, position),
+ "alignaround" : (float, float),
+ "angle" : float,
+ "radius" : float,
+ "crop" : (float, float, float, float),
+ "size" : (int, int),
+ "corner1" : (float, float),
+ "corner2" : (float, float),
+ "subpixel" : bool,
+ "delay" : float,
+ "xoffset" : float,
+ "yoffset" : float,
+ "offset" : (int, int),
+ "xcenter" : position,
+ "ycenter" : position,
+ }
+
+
+def correct_type(v, b, ty):
+ """
+ Corrects the type of v to match ty. b is used to inform the match.
+ """
+
+ if ty is position:
+ if v is None:
+ return None
+ else:
+ return type(b)(v)
+ else:
+ return ty(v)
+
+
+def interpolate(t, a, b, type): #@ReservedAssignment
+ """
+ Linearly interpolate the arguments.
+ """
+
+ if t >= 1.0:
+ return b
+
+ # Recurse into tuples.
+ if isinstance(b, tuple):
+ if a is None:
+ a = [ None ] * len(b)
+
+ return tuple(interpolate(t, i, j, ty) for i, j, ty in zip(a, b, type))
+
+ # Deal with booleans, nones, etc.
+ elif b is None or isinstance(b, bool):
+ if t >= 1.0:
+ return b
+ else:
+ return a
+
+ # Interpolate everything else.
+ else:
+ if a is None:
+ a = 0
+
+ return correct_type(a + t * (b - a), b, type)
+
+# Interpolate the value of a spline. This code is based on Aenakume's code,
+# from 00splines.rpy.
+def interpolate_spline(t, spline):
+
+ if isinstance(spline[-1], tuple):
+ return tuple(interpolate_spline(t, i) for i in zip(*spline))
+
+ if spline[0] is None:
+ return spline[-1]
+
+ if len(spline) == 2:
+ t_p = 1.0 - t
+
+ rv = t_p * spline[0] + t * spline[-1]
+
+ elif len(spline) == 3:
+ t_pp = (1.0 - t)**2
+ t_p = 2 * t * (1.0 - t)
+ t2 = t**2
+
+ rv = t_pp * spline[0] + t_p * spline[1] + t2 * spline[2]
+
+ elif len(spline) == 4:
+
+ t_ppp = (1.0 - t)**3
+ t_pp = 3 * t * (1.0 - t)**2
+ t_p = 3 * t**2 * (1.0 - t)
+ t3 = t**3
+
+ rv = t_ppp * spline[0] + t_pp * spline[1] + t_p * spline[2] + t3 * spline[3]
+
+ else:
+ raise Exception("ATL can't interpolate splines of length %d." % len(spline))
+
+ return correct_type(rv, spline[-1], position)
+
+
+# This is the context used when compiling an ATL statement. It stores the
+# scopes that are used to evaluate the various expressions in the statement,
+# and has a method to do the evaluation and return a result.
+class Context(object):
+ def __init__(self, context):
+ self.context = context
+
+ def eval(self, expr): #@ReservedAssignment
+ expr = renpy.python.escape_unicode(expr)
+ return eval(expr, renpy.store.__dict__, self.context) #@UndefinedVariable
+
+# This is intended to be subclassed by ATLTransform. It takes care of
+# managing ATL execution, which allows ATLTransform itself to not care
+# much about the contents of this file.
+class ATLTransformBase(renpy.object.Object):
+
+ # Compatibility with older saves.
+ parameters = renpy.ast.ParameterInfo([ ], [ ], None, None)
+
+ def __init__(self, atl, context, parameters):
+
+ # The constructor will be called by atltransform.
+
+ if parameters is None:
+ parameters = ATLTransformBase.parameters
+
+ # The parameters that we take.
+ self.parameters = parameters
+
+ # The raw code that makes up this ATL statement.
+ self.atl = atl
+
+ # The context in which execution occurs.
+ self.context = Context(context)
+
+ # The code after it has been compiled into a block.
+ self.block = None
+
+ # The properties of the block, if it contains only an
+ # Interpolation.
+ self.properties = None
+
+ # The state of the statement we are executing. As this can be
+ # shared between more than one object (in the case of a hide),
+ # the data must not be altered.
+ self.atl_state = None
+
+ # Are we done?
+ self.done = False
+
+ # The transform event we are going to process.
+ self.transform_event = None
+
+ # The transform event we last processed.
+ self.last_transform_event = None
+
+ # The child transform event we last processed.
+ self.last_child_transform_event = None
+
+ def take_execution_state(self, t):
+ """
+ Updates self to begin executing from the same point as t. This
+ requires that t.atl is self.atl.
+ """
+
+ super(ATLTransformBase, self).take_execution_state(t)
+
+ if t.atl is not self.atl:
+ return
+
+ self.done = t.done
+ self.block = t.block
+ self.atl_state = t.atl_state
+ self.transform_event = t.transform_event
+ self.last_transform_event = t.last_transform_event
+ self.last_child_transform_event = t.last_child_transform_event
+
+ self.st = t.st
+ self.at = t.at
+ self.st_offset = t.st_offset
+ self.at_offset = t.at_offset
+
+ if self.child is renpy.display.motion.null:
+ self.child = t.child
+
+
+ def __call__(self, *args, **kwargs):
+
+ context = self.context.context.copy()
+
+ for k, v in self.parameters.parameters:
+ if v is not None:
+ context[k] = renpy.python.py_eval(v)
+
+ positional = list(self.parameters.positional)
+ args = list(args)
+
+ child = None
+
+ if not positional and args:
+ child = args.pop(0)
+
+ # Handle positional arguments.
+ while positional and args:
+ name = positional.pop(0)
+ value = args.pop(0)
+
+ if name in kwargs:
+ raise Exception('Parameter %r is used as both a positional and keyword argument to a transition.' % name)
+
+ context[name] = value
+
+ if args:
+ raise Exception("Too many arguments passed to ATL transform.")
+
+ # Handle keyword arguments.
+ for k, v in kwargs.items():
+
+ if k in positional:
+ positional.remove(k)
+ context[k] = v
+ elif k in context:
+ context[k] = v
+ elif k == 'child':
+ child = v
+ else:
+ raise Exception('Parameter %r is not known by ATL Transform.' % k)
+
+ if child is None:
+ child = self.child
+
+ if child is None:
+ child = renpy.display.motion.get_null()
+
+ # Create a new ATL Transform.
+ parameters = renpy.ast.ParameterInfo({}, positional, None, None)
+
+ rv = renpy.display.motion.ATLTransform(
+ atl=self.atl,
+ child=child,
+ style=self.style_arg,
+ context=context,
+ parameters=parameters)
+
+ rv.take_state(self)
+
+ return rv
+
+
+ def compile(self): #@ReservedAssignment
+ """
+ Compiles the ATL code into a block. As necessary, updates the
+ properties.
+ """
+
+ if self.parameters.positional and self.parameters.positional[0][1] is None:
+ raise Exception("Cannot compile ATL Transform, as it's missing positional parameter %s." % self.parameters.positional[0])
+
+ old_exception_info = renpy.game.exception_info
+
+ self.block = self.atl.compile(self.context)
+
+ if len(self.block.statements) == 1 \
+ and isinstance(self.block.statements[0], Interpolation):
+
+ interp = self.block.statements[0]
+
+ if interp.duration == 0 and interp.properties:
+ self.properties = interp.properties[:]
+
+ renpy.game.exception_info = old_exception_info
+
+
+ def execute(self, trans, st, at):
+
+ if self.done:
+ return None
+
+ if not self.block:
+ self.compile()
+
+ # Propagate transform_events from children.
+ if self.child:
+ if self.child.transform_event != self.last_child_transform_event:
+ self.last_child_transform_event = self.child.transform_event
+ self.transform_event = self.child.transform_event
+
+ # Hide request.
+ if trans.hide_request:
+ self.transform_event = "hide"
+
+ if trans.replaced_request:
+ self.transform_event = "replaced"
+
+ # Notice transform events.
+ if self.transform_event != self.last_transform_event:
+ event = self.transform_event
+ self.last_transform_event = self.transform_event
+ else:
+ event = None
+
+ old_exception_info = renpy.game.exception_info
+
+ if self.atl.animation:
+ timebase = at
+ else:
+ timebase = st
+
+ action, arg, pause = self.block.execute(trans, timebase, self.atl_state, event)
+
+ renpy.game.exception_info = old_exception_info
+
+ # print "Executing", self, self.state, self.xpos, self.ypos
+
+ if action == "continue":
+ self.atl_state = arg
+ else:
+ self.done = True
+
+ return pause
+
+ def predict_one(self):
+ self.atl.predict(self.context)
+
+ def visit(self):
+ if not self.block:
+ self.compile()
+
+ return self.children + self.block.visit()
+
+
+# The base class for raw ATL statements.
+class RawStatement(renpy.object.Object):
+
+ def __init__(self, loc):
+ super(RawStatement, self).__init__()
+ self.loc = loc
+
+ # Compiles this RawStatement into a Statement, by using ctx to
+ # evaluate expressions as necessary.
+ def compile(self, ctx): #@ReservedAssignment
+ raise Exception("Compile not implemented.")
+
+ # Predicts the images used by this statement.
+ def predict(self, ctx):
+ return
+
+# The base class for compiled ATL Statements.
+class Statement(renpy.object.Object):
+
+ def __init__(self, loc):
+ super(Statement, self).__init__()
+ self.loc = loc
+
+ # trans is the transform we're working on.
+ # st is the time since this statement started executing.
+ # state is the state stored by this statement, or None if
+ # we've just started executing this statement.
+ # event is an event we're triggering.
+ #
+ # "continue", state, pause - Causes this statement to execute
+ # again, with the given state passed in the second time around.
+ #
+ #
+ # "next", timeleft, pause - Causes the next statement to execute,
+ # with timeleft being the amount of time left after this statement
+ # finished.
+ #
+ # "event", (name, timeleft), pause - Causes an event to be reported,
+ # and control to head up to the event handler.
+ #
+ # "repeat", (count, timeleft), pause - Causes the repeat behavior
+ # to occur.
+ #
+ # As the Repeat statement can only appear in a block, only Block
+ # needs to deal with the repeat behavior.
+ #
+ # Pause is the amount of time until execute should be called again,
+ # or None if there's no need to call execute ever again.
+ def execute(self, trans, st, state, event):
+ raise Exception("Not implemented.")
+
+ # Return a list of displayable children.
+ def visit(self):
+ return [ ]
+
+# This represents a Raw ATL block.
+class RawBlock(RawStatement):
+
+ # Should we use the animation timebase or the showing timebase?
+ animation = False
+
+ def __init__(self, loc, statements, animation):
+
+ super(RawBlock, self).__init__(loc)
+
+ # A list of RawStatements in this block.
+ self.statements = statements
+
+ self.animation = animation
+
+ def compile(self, ctx): #@ReservedAssignment
+ compiling(self.loc)
+
+ statements = [ i.compile(ctx) for i in self.statements ]
+
+ return Block(self.loc, statements)
+
+ def predict(self, ctx):
+ for i in self.statements:
+ i.predict(ctx)
+
+
+# A compiled ATL block.
+class Block(Statement):
+ def __init__(self, loc, statements):
+
+ super(Block, self).__init__(loc)
+
+ # A list of statements in the block.
+ self.statements = statements
+
+ # The start times of various statements.
+ self.times = [ ]
+
+ for i, s in enumerate(statements):
+ if isinstance(s, Time):
+ self.times.append((s.time, i + 1))
+
+ self.times.sort()
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ # Unpack the state.
+ if state is not None:
+ index, start, loop_start, repeats, times, child_state = state
+ else:
+ index, start, loop_start, repeats, times, child_state = 0, 0, 0, 0, self.times[:], None
+
+ # What we might be returning.
+ action = "continue"
+ arg = None
+ pause = None
+
+ while action == "continue":
+
+ # Target is the time we're willing to execute to.
+ # Max_pause is how long we'll wait before executing again.
+
+ # If we have times queued up, then use them to inform target
+ # and time.
+ if times:
+ time, tindex = times[0]
+ target = min(time, st)
+ max_pause = time - target
+
+ # Otherwise, take the defaults.
+ else:
+ target = st
+ max_pause = 15
+
+ while True:
+
+ # If we've hit the last statement, it's the end of
+ # this block.
+ if index >= len(self.statements):
+ return "next", target - start, None
+
+
+ # Find the statement and try to run it.
+ stmt = self.statements[index]
+ action, arg, pause = stmt.execute(trans, target - start, child_state, event)
+
+ # On continue, persist our state.
+ if action == "continue":
+ if pause is None:
+ pause = max_pause
+
+ action, arg, pause = "continue", (index, start, loop_start, repeats, times, arg), min(max_pause, pause)
+ break
+
+ elif action == "event":
+ return action, arg, pause
+
+ # On next, advance to the next statement in the block.
+ elif action == "next":
+ index += 1
+ start = target - arg
+ child_state = None
+
+ # On repeat, either terminate the block, or go to
+ # the first statement.
+ elif action == "repeat":
+
+ count, arg = arg
+ loop_end = target - arg
+ duration = loop_end - loop_start
+
+ if duration <= 0:
+ raise Exception("ATL appears to be in an infinite loop.")
+
+ # Figure how many durations can occur between the
+ # start of the loop and now.
+ new_repeats = int((target - loop_start) / duration)
+
+ if count is not None:
+ if repeats + new_repeats >= count:
+ new_repeats = count - repeats
+ loop_start += new_repeats * duration
+ return "next", target - loop_start, None
+
+ repeats += new_repeats
+ loop_start = loop_start + new_repeats * duration
+ start = loop_start
+ index = 0
+ child_state = None
+
+ if times:
+ time, tindex = times[0]
+ if time <= target:
+ times.pop(0)
+
+ index = tindex
+ start = time
+ child_state = None
+
+ continue
+
+ return action, arg, pause
+
+ def visit(self):
+ return [ j for i in self.statements for j in i.visit() ]
+
+# This can become one of four things:
+#
+# - A pause.
+# - An interpolation (which optionally can also reference other
+# blocks, as long as they're not time-dependent, and have the same
+# arity as the interpolation).
+# - A call to another block.
+# - A command to change the image, perhaps with a transition.
+#
+# We won't decide which it is until runtime, as we need the
+# values of the variables here.
+class RawMultipurpose(RawStatement):
+
+ warp_function = None
+
+ def __init__(self, loc):
+
+ super(RawMultipurpose, self).__init__(loc)
+
+ self.warper = None
+ self.duration = None
+ self.properties = [ ]
+ self.expressions = [ ]
+ self.splines = [ ]
+ self.revolution = None
+ self.circles = "0"
+
+ def add_warper(self, name, duration, warp_function):
+ self.warper = name
+ self.duration = duration
+ self.warp_function = warp_function
+
+ def add_property(self, name, exprs):
+ self.properties.append((name, exprs))
+
+ def add_expression(self, expr, with_clause):
+ self.expressions.append((expr, with_clause))
+
+ def add_revolution(self, revolution):
+ self.revolution = revolution
+
+ def add_circles(self, circles):
+ self.circles = circles
+
+ def add_spline(self, name, exprs):
+ self.splines.append((name, exprs))
+
+ def compile(self, ctx): #@ReservedAssignment
+
+ compiling(self.loc)
+
+ # Figure out what kind of statement we have. If there's no
+ # interpolator, and no properties, than we have either a
+ # call, or a child statement.
+ if (self.warper is None and
+ self.warp_function is None and
+ not self.properties and
+ not self.splines and
+ len(self.expressions) == 1):
+
+ expr, withexpr = self.expressions[0]
+
+ child = ctx.eval(expr)
+ if withexpr:
+ transition = ctx.eval(withexpr)
+ else:
+ transition = None
+
+ if isinstance(child, (int, float)):
+ return Interpolation(self.loc, "pause", child, [ ], None, 0, [ ])
+
+ if isinstance(child, ATLTransformBase):
+ child.compile()
+ return child.block
+
+ else:
+ return Child(self.loc, child, transition)
+
+ compiling(self.loc)
+
+ # Otherwise, we probably have an interpolation statement.
+
+ if self.warp_function:
+ warper = ctx.eval(self.warp_function)
+ else:
+ warper = self.warper or "pause"
+
+ if warper not in warpers:
+ raise Exception("ATL Warper %s is unknown at runtime." % warper)
+
+ properties = [ ]
+
+ for name, expr in self.properties:
+ if name not in PROPERTIES:
+ raise Exception("ATL Property %s is unknown at runtime." % property)
+
+ value = ctx.eval(expr)
+ properties.append((name, value))
+
+ splines = [ ]
+
+ for name, exprs in self.splines:
+ if name not in PROPERTIES:
+ raise Exception("ATL Property %s is unknown at runtime." % property)
+
+ values = [ ctx.eval(i) for i in exprs ]
+
+ splines.append((name, values))
+
+ for expr, _with in self.expressions:
+ try:
+ value = ctx.eval(expr)
+ except:
+ raise Exception("Could not evaluate expression %r when compiling ATL." % expr)
+
+ if not isinstance(value, ATLTransformBase):
+ raise Exception("Expression %r is not an ATL transform, and so cannot be included in an ATL interpolation." % expr)
+
+ value.compile()
+
+ if value.properties is None:
+ raise Exception("ATL transform %r is too complicated to be included in interpolation." % expr)
+
+
+ properties.extend(value.properties)
+
+ duration = ctx.eval(self.duration)
+ circles = ctx.eval(self.circles)
+
+ return Interpolation(self.loc, warper, duration, properties, self.revolution, circles, splines)
+
+ def predict(self, ctx):
+
+ for i, _j in self.expressions:
+
+ try:
+ i = ctx.eval(i)
+ except:
+ continue
+
+ if isinstance(i, ATLTransformBase):
+ i.atl.predict(ctx)
+ return
+
+ try:
+ renpy.easy.predict(i)
+ except:
+ continue
+
+# This lets us have an ATL transform as our child.
+class RawContainsExpr(RawStatement):
+
+ def __init__(self, loc, expr):
+
+ super(RawContainsExpr, self).__init__(loc)
+
+ self.expression = expr
+
+ def compile(self, ctx): #@ReservedAssignment
+ compiling(self.loc)
+ child = ctx.eval(self.expression)
+ return Child(self.loc, child, None)
+
+
+# This allows us to have multiple children, inside a Fixed.
+class RawChild(RawStatement):
+
+ def __init__(self, loc, child):
+
+ super(RawChild, self).__init__(loc)
+
+ self.children = [ child ]
+
+ def compile(self, ctx): #@ReservedAssignment
+ box = renpy.display.layout.MultiBox(layout='fixed')
+
+ for i in self.children:
+ box.add(renpy.display.motion.ATLTransform(i, context=ctx.context))
+
+ return Child(self.loc, box, None)
+
+
+# This changes the child of this statement, optionally with a transition.
+class Child(Statement):
+
+ def __init__(self, loc, child, transition):
+
+ super(Child, self).__init__(loc)
+
+ self.child = renpy.easy.displayable(child)
+ self.transition = transition
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ old_child = trans.raw_child
+
+ if (old_child is not None) and (old_child is not renpy.display.motion.null) and (self.transition is not None):
+ child = self.transition(old_widget=old_child,
+ new_widget=self.child)
+ else:
+ child = self.child
+
+ trans.set_child(child)
+ trans.raw_child = self.child
+
+ return "next", st, None
+
+ def visit(self):
+ return [ self.child ]
+
+
+# This causes interpolation to occur.
+class Interpolation(Statement):
+
+ def __init__(self, loc, warper, duration, properties, revolution, circles, splines):
+
+ super(Interpolation, self).__init__(loc)
+
+ self.warper = warper
+ self.duration = duration
+ self.properties = properties
+ self.splines = splines
+
+ # The direction we revolve in: cw, ccw, or None.
+ self.revolution = revolution
+
+ # The number of complete circles we make.
+ self.circles = circles
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ warper = warpers.get(self.warper, self.warper)
+
+ if self.duration:
+ complete = min(1.0, st / self.duration)
+ else:
+ complete = 1.0
+
+ complete = warper(complete)
+
+ if state is None:
+
+ # Create a new transform state, and apply the property
+ # changes to it.
+ newts = renpy.display.motion.TransformState()
+ newts.take_state(trans.state)
+
+ for k, v in self.properties:
+ setattr(newts, k, v)
+
+ # Now, the things we change linearly are in the difference
+ # between the new and old states.
+ linear = trans.state.diff(newts)
+
+ revolution = None
+ splines = [ ]
+
+ # Clockwise revolution.
+ if self.revolution is not None:
+
+ # Remove various irrelevant motions.
+ for i in [ 'xpos', 'ypos',
+ 'xanchor', 'yanchor',
+ 'xaround', 'yaround',
+ 'xanchoraround', 'yanchoraround',
+ ]:
+
+ linear.pop(i, None)
+
+ if newts.xaround is not None:
+
+ # Ensure we rotate around the new point.
+ trans.state.xaround = newts.xaround
+ trans.state.yaround = newts.yaround
+ trans.state.xanchoraround = newts.xanchoraround
+ trans.state.yanchoraround = newts.yanchoraround
+
+ # Get the start and end angles and radii.
+ startangle = trans.state.angle
+ endangle = newts.angle
+ startradius = trans.state.radius
+ endradius = newts.radius
+
+ # Make sure the revolution is in the appropriate direction,
+ # and contains an appropriate number of circles.
+
+ if self.revolution == "clockwise":
+ if endangle < startangle:
+ startangle -= 360
+
+ startangle -= self.circles * 360
+
+ elif self.revolution == "counterclockwise":
+ if endangle > startangle:
+ startangle += 360
+
+ startangle += self.circles * 360
+
+ # Store the revolution.
+ revolution = (startangle, endangle, startradius, endradius)
+
+ # Figure out the splines.
+ for name, values in self.splines:
+ splines.append((name, [ getattr(trans.state, name) ] + values))
+
+ state = (linear, revolution, splines)
+
+ # Ensure that we set things, even if they don't actually
+ # change from the old state.
+ for k, v in self.properties:
+ if k not in linear:
+ setattr(trans.state, k, v)
+
+ else:
+ linear, revolution, splines = state
+
+ # Linearly interpolate between the things in linear.
+ for k, (old, new) in linear.items():
+ value = interpolate(complete, old, new, PROPERTIES[k])
+
+ setattr(trans.state, k, value)
+
+ # Handle the revolution.
+ if revolution is not None:
+ startangle, endangle, startradius, endradius = revolution
+ trans.state.angle = interpolate(complete, startangle, endangle, float)
+ trans.state.radius = interpolate(complete, startradius, endradius, float)
+
+
+ # Handle any splines we might have.
+ for name, values in splines:
+ value = interpolate_spline(complete, values)
+ setattr(trans.state, name, value)
+
+ if st >= self.duration:
+ return "next", st - self.duration, None
+ else:
+ if not self.properties and not self.revolution and not self.splines:
+ return "continue", state, self.duration - st
+ else:
+ return "continue", state, 0
+
+
+# Implementation of the repeat statement.
+class RawRepeat(RawStatement):
+
+ def __init__(self, loc, repeats):
+
+ super(RawRepeat, self).__init__(loc)
+
+ self.repeats = repeats
+
+ def compile(self, ctx): #@ReservedAssignment
+
+ compiling(self.loc)
+
+ repeats = self.repeats
+
+ if repeats is not None:
+ repeats = ctx.eval(repeats)
+
+ return Repeat(self.loc, repeats)
+
+class Repeat(Statement):
+
+ def __init__(self, loc, repeats):
+
+ super(Repeat, self).__init__(loc)
+
+ self.repeats = repeats
+
+ def execute(self, trans, st, state, event):
+ return "repeat", (self.repeats, st), 0
+
+
+# Parallel statement.
+
+class RawParallel(RawStatement):
+
+ def __init__(self, loc, block):
+
+ super(RawParallel, self).__init__(loc)
+ self.blocks = [ block ]
+
+ def compile(self, ctx): #@ReservedAssignment
+ return Parallel(self.loc, [i.compile(ctx) for i in self.blocks])
+
+ def predict(self, ctx):
+ for i in self.blocks:
+ i.predict(ctx)
+
+
+class Parallel(Statement):
+
+ def __init__(self, loc, blocks):
+ super(Parallel, self).__init__(loc)
+ self.blocks = blocks
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ if state is None:
+ state = [ (i, None) for i in self.blocks ]
+
+ # The amount of time left after finishing this block.
+ left = [ ]
+
+ # The duration of the pause.
+ pauses = [ ]
+
+ # The new state structure.
+ newstate = [ ]
+
+ for i, istate in state:
+
+ action, arg, pause = i.execute(trans, st, istate, event)
+
+ if pause is not None:
+ pauses.append(pause)
+
+ if action == "continue":
+ newstate.append((i, arg))
+ elif action == "next":
+ left.append(arg)
+ elif action == "event":
+ return action, arg, pause
+
+ if newstate:
+ return "continue", newstate, min(pauses)
+ else:
+ return "next", min(left), None
+
+ def visit(self):
+ return [ j for i in self.blocks for j in i.visit() ]
+
+
+# The choice statement.
+
+class RawChoice(RawStatement):
+
+ def __init__(self, loc, chance, block):
+ super(RawChoice, self).__init__(loc)
+
+ self.choices = [ (chance, block) ]
+
+ def compile(self, ctx): #@ReservedAssignment
+ compiling(self.loc)
+ return Choice(self.loc, [ (ctx.eval(chance), block.compile(ctx)) for chance, block in self.choices])
+
+ def predict(self, ctx):
+ for _i, j in self.choices:
+ j.predict(ctx)
+
+class Choice(Statement):
+
+ def __init__(self, loc, choices):
+
+ super(Choice, self).__init__(loc)
+
+ self.choices = choices
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ if state is None:
+
+ total = 0
+ for chance, choice in self.choices:
+ total += chance
+
+ n = random.uniform(0, total)
+
+ for chance, choice in self.choices:
+ if n < chance:
+ break
+ n -= chance
+
+ cstate = None
+
+ else:
+ choice, cstate = state
+
+ action, arg, pause = choice.execute(trans, st, cstate, event)
+
+ if action == "continue":
+ return "continue", (choice, arg), pause
+ else:
+ return action, arg, None
+
+ def visit(self):
+ return [ j for i in self.choices for j in i[1].visit() ]
+
+
+# The Time statement.
+
+class RawTime(RawStatement):
+
+ def __init__(self, loc, time):
+
+ super(RawTime, self).__init__(loc)
+ self.time = time
+
+ def compile(self, ctx): #@ReservedAssignment
+ compiling(self.loc)
+ return Time(self.loc, ctx.eval(self.time))
+
+class Time(Statement):
+
+ def __init__(self, loc, time):
+ super(Time, self).__init__(loc)
+
+ self.time = time
+
+ def execute(self, trans, st, state, event):
+ return "continue", None, None
+
+
+# The On statement.
+
+class RawOn(RawStatement):
+
+ def __init__(self, loc, name, block):
+ super(RawOn, self).__init__(loc)
+
+ self.handlers = { name : block }
+
+ def compile(self, ctx): #@ReservedAssignment
+
+ compiling(self.loc)
+
+ handlers = { }
+
+ for k, v in self.handlers.items():
+ handlers[k] = v.compile(ctx)
+
+ return On(self.loc, handlers)
+
+ def predict(self, ctx):
+ for i in self.handlers.values():
+ i.predict(ctx)
+
+class On(Statement):
+
+ def __init__(self, loc, handlers):
+ super(On, self).__init__(loc)
+
+ self.handlers = handlers
+
+ def execute(self, trans, st, state, event):
+
+ executing(self.loc)
+
+ # If it's our first time through, start in the start state.
+ if state is None:
+ name, start, cstate = ("start", st, None)
+ else:
+ name, start, cstate = state
+
+ # If we have an external event, and we have a handler for it,
+ # handle it.
+ if event in self.handlers:
+
+ # Do not allow people to abort the hide handler with another
+ # event.
+ if name != "hide":
+ name = event
+ start = st
+ cstate = None
+
+
+ while True:
+
+ # If we don't have a handler, return until we change event.
+ if name not in self.handlers:
+ return "continue", (name, start, cstate), None
+
+ action, arg, pause = self.handlers[name].execute(trans, st - start, cstate, event)
+
+ # If we get a continue, save our state.
+ if action == "continue":
+
+ # If it comes from a hide block, indicate that.
+ if name == "hide" or name == "replaced":
+ trans.hide_response = False
+ trans.replaced_response = False
+
+ return "continue", (name, start, arg), pause
+
+ # If we get a next, then try going to the default
+ # event, unless we're already in default, in which case we
+ # go to None.
+ elif action == "next":
+ if name == "default" or name == "hide" or name == "replaced":
+ name = None
+ else:
+ name = "default"
+
+ start = st - arg
+ cstate = None
+
+ continue
+
+ # If we get an event, then either handle it if we can, or
+ # pass it up the stack if we can't.
+ elif action == "event":
+
+ name, arg = arg
+
+ if name in self.handlers:
+ start = max(st - arg, st - 30)
+ cstate = None
+ continue
+
+ return "event", (name, arg), None
+
+ def visit(self):
+ return [ j for i in self.handlers.values() for j in i.visit() ]
+
+
+# Event statement.
+
+class RawEvent(RawStatement):
+
+ def __init__(self, loc, name):
+ super(RawEvent, self).__init__(loc)
+
+ self.name = name
+
+ def compile(self, ctx): #@ReservedAssignment
+ return Event(self.loc, self.name)
+
+
+class Event(Statement):
+
+ def __init__(self, loc, name):
+ super(Event, self).__init__(loc)
+
+ self.name = name
+
+ def execute(self, trans, st, state, event):
+ return "event", (self.name, st), None
+
+
+class RawFunction(RawStatement):
+
+ def __init__(self, loc, expr):
+ super(RawFunction, self).__init__(loc)
+
+ self.expr = expr
+
+ def compile(self, ctx): #@ReservedAssignment
+ compiling(self.loc)
+ return Function(self.loc, ctx.eval(self.expr))
+
+
+class Function(Statement):
+
+ def __init__(self, loc, function):
+ super(Function, self).__init__(loc)
+
+ self.function = function
+
+ def execute(self, trans, st, state, event):
+ fr = self.function(trans, st, trans.at)
+
+ if fr is not None:
+ return "continue", None, fr
+ else:
+ return "next", 0, None
+
+
+# This parses an ATL block.
+def parse_atl(l):
+
+ l.advance()
+ block_loc = l.get_location()
+
+ statements = [ ]
+
+ animation = False
+
+ while not l.eob:
+
+ loc = l.get_location()
+
+ if l.keyword('repeat'):
+
+ repeats = l.simple_expression()
+ statements.append(RawRepeat(loc, repeats))
+
+ elif l.keyword('block'):
+ l.require(':')
+ l.expect_eol()
+ l.expect_block('block')
+
+ block = parse_atl(l.subblock_lexer())
+ statements.append(block)
+
+ elif l.keyword('contains'):
+
+ expr = l.simple_expression()
+
+ if expr:
+
+ l.expect_noblock('contains expression')
+ statements.append(RawContainsExpr(loc, expr))
+
+ else:
+
+ l.require(':')
+ l.expect_eol()
+ l.expect_block('contains')
+
+ block = parse_atl(l.subblock_lexer())
+ statements.append(RawChild(loc, block))
+
+ elif l.keyword('parallel'):
+ l.require(':')
+ l.expect_eol()
+ l.expect_block('parallel')
+
+ block = parse_atl(l.subblock_lexer())
+ statements.append(RawParallel(loc, block))
+
+ elif l.keyword('choice'):
+
+ chance = l.simple_expression()
+ if not chance:
+ chance = "1.0"
+
+ l.require(':')
+ l.expect_eol()
+ l.expect_block('choice')
+
+ block = parse_atl(l.subblock_lexer())
+ statements.append(RawChoice(loc, chance, block))
+
+ elif l.keyword('on'):
+
+ name = l.require(l.word)
+
+ l.require(':')
+ l.expect_eol()
+ l.expect_block('on')
+
+ block = parse_atl(l.subblock_lexer())
+ statements.append(RawOn(loc, name, block))
+
+ elif l.keyword('time'):
+ time = l.require(l.simple_expression)
+ l.expect_noblock('time')
+
+ statements.append(RawTime(loc, time))
+
+ elif l.keyword('function'):
+ expr = l.require(l.simple_expression)
+ l.expect_noblock('function')
+
+ statements.append(RawFunction(loc, expr))
+
+ elif l.keyword('event'):
+ name = l.require(l.word)
+ l.expect_noblock('event')
+
+ statements.append(RawEvent(loc, name))
+
+ elif l.keyword('pass'):
+ l.expect_noblock('pass')
+ statements.append(None)
+
+ elif l.keyword('animation'):
+ l.expect_noblock('animation')
+ animation = True
+
+ else:
+
+ # If we can't assign it it a statement more specifically,
+ # we try to parse it into a RawMultipurpose. That will
+ # then be turned into another statement, as appropriate.
+
+ # The RawMultipurpose we add things to.
+ rm = renpy.atl.RawMultipurpose(loc)
+
+ # Is the last clause an expression?
+ last_expression = False
+
+ # Is this clause an expression?
+ this_expression = False
+
+ # First, look for a warper.
+ cp = l.checkpoint()
+ warper = l.name()
+
+
+ if warper in warpers:
+ duration = l.require(l.simple_expression)
+ warp_function = None
+
+ elif warper == "warp":
+
+ warper = None
+ warp_function = l.require(l.simple_expression)
+ duration = l.require(l.simple_expression)
+
+ else:
+ l.revert(cp)
+
+ warper = None
+ warp_function = None
+ duration = "0"
+
+ rm.add_warper(warper, duration, warp_function)
+
+ # Now, look for properties and simple_expressions.
+ while True:
+
+ # Update expression status.
+ last_expression = this_expression
+ this_expression = False
+
+ if l.keyword('pass'):
+ continue
+
+ # Parse revolution keywords.
+ if l.keyword('clockwise'):
+ rm.add_revolution('clockwise')
+ continue
+
+ if l.keyword('counterclockwise'):
+ rm.add_revolution('counterclockwise')
+ continue
+
+ if l.keyword('circles'):
+ expr = l.require(l.simple_expression)
+ rm.add_circles(expr)
+
+ # Try to parse a property.
+ cp = l.checkpoint()
+
+ prop = l.name()
+
+ if prop in PROPERTIES:
+
+ expr = l.require(l.simple_expression)
+
+ # We either have a property or a spline. It's the
+ # presence of knots that determine which one it is.
+
+ knots = [ ]
+
+ while l.keyword('knot'):
+ knots.append(l.require(l.simple_expression))
+
+ if knots:
+ knots.append(expr)
+ rm.add_spline(prop, knots)
+ else:
+ rm.add_property(prop, expr)
+
+ continue
+
+ # Otherwise, try to parse it as a simple expressoon,
+ # with an optional with clause.
+
+ l.revert(cp)
+
+ expr = l.simple_expression()
+
+ if not expr:
+ break
+
+ if last_expression:
+ l.error('ATL statement contains two expressions in a row; is one of them a misspelled property? If not, separate them with pass.')
+
+ this_expression = True
+
+ if l.keyword("with"):
+ with_expr = l.require(l.simple_expression)
+ else:
+ with_expr = None
+
+ rm.add_expression(expr, with_expr)
+
+ l.expect_noblock('ATL')
+
+ statements.append(rm)
+
+
+ if l.eol():
+ l.advance()
+ continue
+
+ l.require(",", "comma or end of line")
+
+
+ # Merge together statements that need to be merged together.
+
+ merged = [ ]
+ old = None
+
+ for new in statements:
+
+ if isinstance(old, RawParallel) and isinstance(new, RawParallel):
+ old.blocks.extend(new.blocks)
+ continue
+
+ elif isinstance(old, RawChoice) and isinstance(new, RawChoice):
+ old.choices.extend(new.choices)
+ continue
+
+ elif isinstance(old, RawChild) and isinstance(new, RawChild):
+ old.children.extend(new.children)
+ continue
+
+ elif isinstance(old, RawOn) and isinstance(new, RawOn):
+ old.handlers.update(new.handlers)
+ continue
+
+ # None is a pause statement, which gets skipped, but also
+ # prevents things from combining.
+ elif new is None:
+ old = new
+ continue
+
+ merged.append(new)
+ old = new
+
+ return RawBlock(block_loc, merged, animation)
diff --git a/unrpyc/renpy/display/__init__.py b/unrpyc/renpy/display/__init__.py
new file mode 100644
index 0000000..055dad6
--- /dev/null
+++ b/unrpyc/renpy/display/__init__.py
@@ -0,0 +1,39 @@
+# 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.
+
+import renpy.log
+
+# The draw object through which all drawing is routed. This object
+# contains all of the distinction between the software and GL
+# renderers.
+draw = None
+
+# The interface object.
+interface = None
+
+# Should we disable imagedissolve-type transitions?
+less_imagedissolve = False
+
+# Logs we use.
+log = renpy.log.open("log", developer=False, append=False)
+ic_log = renpy.log.open("image_cache", developer=True, append=False)
+to_log = renpy.log.open("text_overflow", developer=True, append=True)
+
diff --git a/unrpyc/renpy/display/accelerator.pyx b/unrpyc/renpy/display/accelerator.pyx
new file mode 100644
index 0000000..8c4172c
--- /dev/null
+++ b/unrpyc/renpy/display/accelerator.pyx
@@ -0,0 +1,296 @@
+#cython: profile=False
+# 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.
+
+import renpy
+import math
+from renpy.display.render cimport Render, Matrix2D, render
+
+
+################################################################################
+# Surface copying
+################################################################################
+
+from pygame cimport *
+
+def nogil_copy(src, dest):
+ """
+ Does a gil-less blit of src to dest, with minimal locking.
+ """
+
+ cdef SDL_Surface *src_surf
+ cdef SDL_Surface *dst_surf
+
+ src_surf = PySurface_AsSurface(src)
+ dest_surf = PySurface_AsSurface(dest)
+
+ old_alpha = src_surf.flags & SDL_SRCALPHA
+
+ if old_alpha:
+ SDL_SetAlpha(src_surf, 0, 255)
+
+ with nogil:
+ SDL_BlitSurface(src_surf, NULL, dest_surf, NULL)
+
+ if old_alpha:
+ SDL_SetAlpha(src_surf, SDL_SRCALPHA, 255)
+
+
+
+################################################################################
+# Transform render function
+################################################################################
+
+cdef Matrix2D IDENTITY
+IDENTITY = renpy.display.render.IDENTITY
+
+# This file contains implementations of methods of classes that
+# are found in other files, for performance reasons.
+
+def transform_render(self, widtho, heighto, st, at):
+
+ cdef double rxdx, rxdy, rydx, rydy
+ cdef double cosa, sina
+ cdef double xo, x1, x2, x3, px
+ cdef double yo, y1, y2, y3, py
+ cdef float zoom, xzoom, yzoom
+ cdef double cw, ch, nw, nh
+ cdef Render rv, cr
+ cdef double angle
+ cdef double alpha
+ cdef double width = widtho
+ cdef double height = heighto
+
+ # Should we perform clipping?
+ clipping = False
+
+ # Prevent time from ticking backwards, as can happen if we replace a
+ # transform but keep its state.
+ if st + self.st_offset <= self.st:
+ self.st_offset = self.st - st
+ if at + self.at_offset <= self.at:
+ self.at_offset = self.at - at
+
+ self.st = st = st + self.st_offset
+ self.at = at = at + self.at_offset
+
+ # Update the state.
+ self.update_state()
+
+ # Render the child.
+ child = self.child
+
+ if child is None:
+ raise Exception("Transform does not have a child.")
+
+ state = self.state
+
+ if state.size:
+ widtho, heighto = state.size
+
+ cr = render(child, widtho, heighto, st - self.child_st_base, at)
+
+ width = cr.width
+ height = cr.height
+
+ self.child_size = width, height
+
+ # The reverse matrix.
+ rxdx = 1
+ rxdy = 0
+ rydx = 0
+ rydy = 1
+
+ xo = 0
+ yo = 0
+
+ # Cropping.
+ crop = state.crop
+ if (state.corner1 is not None) and (crop is None) and (state.corner2 is not None):
+ x1, y1 = state.corner1
+ x2, y2 = state.corner2
+
+ if x1 > x2:
+ x3 = x1
+ x1 = x2
+ x2 = x3
+ if y1 > y2:
+ y3 = y1
+ y1 = y2
+ y2 = y3
+
+ crop = (x1, y1, x2-x1, y2-y1)
+
+ if crop is not None:
+
+ negative_xo, negative_yo, width, height = crop
+
+ if state.rotate:
+ clipcr = Render(width, height)
+ clipcr.subpixel_blit(cr, (-negative_xo, -negative_yo))
+ clipcr.clipping = True
+ cr = clipcr
+ else:
+ xo = -negative_xo
+ yo = -negative_yo
+ clipping = True
+
+ # Size.
+ size = state.size
+ if (size is not None) and (size != (width, height)):
+ nw, nh = size
+ xzoom = 1.0 * nw / width
+ yzoom = 1.0 * nh / height
+
+ rxdx = xzoom
+ rydy = yzoom
+
+ xo *= xzoom
+ yo *= yzoom
+
+ width, height = size
+
+ # zoom
+ zoom = state.zoom
+ xzoom = zoom * <double> state.xzoom
+ yzoom = zoom * <double> state.yzoom
+
+ if xzoom != 1:
+
+ rxdx *= xzoom
+
+ if xzoom < 0:
+ width *= -xzoom
+ else:
+ width *= xzoom
+
+ xo *= xzoom
+ # origin corrections for flipping
+ if xzoom < 0:
+ xo += width
+
+ if yzoom != 1:
+
+ rydy *= yzoom
+
+ if yzoom < 0:
+ height *= -yzoom
+ else:
+ height *= yzoom
+
+ yo *= yzoom
+ # origin corrections for flipping
+ if yzoom < 0:
+ yo += height
+
+ # Rotation.
+ rotate = state.rotate
+ if rotate is not None:
+
+ cw = width
+ ch = height
+
+ angle = rotate * 3.1415926535897931 / 180
+
+ cosa = math.cos(angle)
+ sina = math.sin(angle)
+
+ # reverse = Matrix2D(xdx, xdy, ydx, ydy) * reverse
+
+ # We know that at this point, rxdy and rydx are both 0, so
+ # we can simplify these formulae a bit.
+ rxdy = rydy * -sina
+ rydx = rxdx * sina
+ rxdx *= cosa
+ rydy *= cosa
+
+ # first corner point (changes with flipping)
+ px = cw / 2.0
+ if xzoom < 0:
+ px = -px
+ py = ch / 2.0
+ if yzoom < 0:
+ py = -py
+
+ if state.rotate_pad:
+ width = height = math.hypot(cw, ch)
+
+ xo = -px * cosa + py * sina
+ yo = -px * sina - py * cosa
+
+ else:
+ xo = -px * cosa + py * sina
+ yo = -px * sina - py * cosa
+
+ x2 = -px * cosa - py * sina
+ y2 = -px * sina + py * cosa
+
+ x3 = px * cosa - py * sina
+ y3 = px * sina + py * cosa
+
+ x4 = px * cosa + py * sina
+ y4 = px * sina - py * cosa
+
+ width = max(xo, x2, x3, x4) - min(xo, x2, x3, x4)
+ height = max(yo, y2, y3, y4) - min(yo, y2, y3, y4)
+
+ xo += width / 2.0
+ yo += height / 2.0
+
+ alpha = state.alpha
+
+ rv = Render(width, height)
+
+ # Default case - no transformation matrix.
+ if rxdx == 1 and rxdy == 0 and rydx == 0 and rydy == 1:
+ self.forward = IDENTITY
+ self.reverse = IDENTITY
+
+ else:
+
+ self.reverse = rv.reverse = Matrix2D(rxdx, rxdy, rydx, rydy)
+
+ inv_det = rxdx * rydy - rxdy * rydx
+
+ if not inv_det:
+ self.forward = rv.forward = Matrix2D(0, 0, 0, 0)
+ else:
+ self.forward = rv.forward = Matrix2D(
+ rydy / inv_det,
+ -rxdy / inv_det,
+ -rydx / inv_det,
+ rxdx / inv_det)
+
+ rv.alpha = alpha
+ rv.clipping = clipping
+
+ pos = (xo, yo)
+
+ if state.subpixel:
+ rv.subpixel_blit(cr, pos)
+ else:
+ rv.blit(cr, pos)
+
+ self.offsets = [ pos ]
+ self.render_size = (width, height)
+
+ return rv
+
diff --git a/unrpyc/renpy/display/anim.py b/unrpyc/renpy/display/anim.py
new file mode 100644
index 0000000..de1398a
--- /dev/null
+++ b/unrpyc/renpy/display/anim.py
@@ -0,0 +1,634 @@
+# 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 support for state-machine controlled animations.
+
+import renpy.display
+import random
+
+class State(object):
+ """
+ This creates a state that can be used in a SMAnimation.
+ """
+
+
+ def __init__(self, name, image, *atlist, **properties):
+ """
+ @param name: A string giving the name of this state.
+
+ @param image: The displayable that is shown to the user while
+ we are in (entering) this state. For convenience, this can
+ also be a string or tuple, which is interpreted with Image.
+
+ image should be None when this State is used with motion,
+ to indicate that the image will be replaced with the child of
+ the motion.
+
+ @param atlist: A list of functions to call on the image. (In
+ general, if something can be used in an at clause, it can be
+ used here as well.)
+
+ If any keyword arguments are given, they are used to construct a
+ Position object, that modifies the position of the image.
+ """
+
+ if image and not isinstance(image, renpy.display.core.Displayable):
+ image = renpy.easy.displayable(image)
+
+ self.name = name
+ self.image = image
+ self.atlist = atlist
+ self.properties = properties
+
+
+ def add(self, sma):
+ sma.states[self.name] = self
+
+ def get_image(self):
+ rv = self.image
+
+ for i in self.atlist:
+ rv = i(rv)
+
+ if self.properties:
+ rv = renpy.display.layout.Position(rv, **self.properties)
+
+ return rv
+
+ def motion_copy(self, child):
+
+ if self.image is not None:
+ child = self.image
+
+ return State(self.name, child, *self.atlist)
+
+
+class Edge(object):
+ """
+ This creates an edge that can be used with a SMAnimation.
+ """
+
+ def __init__(self, old, delay, new, trans=None, prob=1):
+ """
+ @param old: The name (a string) of the state that this transition is from.
+
+ @param delay: The number of seconds that this transition takes.
+
+ @param new: The name (a string) of the state that this transition is to.
+
+ @param trans: The transition that will be used to show the
+ image found in the new state. If None, the image is show
+ immediately.
+
+ When used with an SMMotion, the transition should probably be
+ move.
+
+ @param prob: The number of times this edge is added. This can
+ be used to make a transition more probable then others. For
+ example, if one transition out of a state has prob=5, and the
+ other has prob=1, then the one with prob=5 will execute 5/6 of
+ the time, while the one with prob=1 will only occur 1/6 of the
+ time. (Don't make this too large, as memory use is proportional to
+ this value.)
+ """
+
+ self.old = old
+ self.delay = delay
+ self.new = new
+ self.trans = trans
+ self.prob = prob
+
+ def add(self, sma):
+ for _i in range(0, self.prob):
+ sma.edges.setdefault(self.old, []).append(self)
+
+
+class SMAnimation(renpy.display.core.Displayable):
+ """
+ This creates a state-machine animation. Such an animation is
+ created by randomly traversing the edges between states in a
+ defined state machine. Each state corresponds to an image shown to
+ the user, with the edges corresponding to the amount of time an
+ image is shown, and the transition it is shown with.
+
+ Images are shown, perhaps with a transition, when we are
+ transitioning into a state containing that image.
+ """
+
+ def __init__(self, initial, *args, **properties):
+ """
+ @param initial: The name (a string) of the initial state we
+ start in.
+
+ @param showold: If the keyword parameter showold is True, then
+ the old image is shown instead of the new image when in an
+ edge.
+
+ @param anim_timebase: If True, we use the animation
+ timebase. If False, we use the displayable timebase.
+
+ This accepts as additional arguments the anim.State and
+ anim.Edge objects that are used to make up this state
+ machine.
+ """
+
+ if 'delay' in properties:
+ self.delay = properties['delay']
+ del properties['delay']
+ else:
+ self.delay = None
+
+ if 'showold' in properties:
+ self.showold = properties['showold']
+ del properties['showold']
+ else:
+ self.showold = False
+
+ if 'anim_timebase' in properties:
+ self.anim_timebase = properties['anim_timebase']
+ del properties['anim_timebase']
+ else:
+ self.anim_timebase = True
+
+ super(SMAnimation, self).__init__(**properties)
+
+ self.properties = properties
+
+ # The initial state.
+ self.initial = initial
+
+ # A map from state name to State object.
+ self.states = { }
+
+ # A map from state name to list of Edge objects.
+ self.edges = { }
+
+ for i in args:
+ i.add(self)
+
+ # The time at which the current edge started. If None, will be
+ # set to st by render.
+ self.edge_start = None
+
+ # A cache for what the current edge looks like when rendered.
+ self.edge_cache = None
+
+ # The current edge.
+ self.edge = None
+
+ # The state we're in.
+ self.state = None
+
+ def visit(self):
+ return [ i.image for i in self.states.values() ]
+
+ def pick_edge(self, state):
+ """
+ This randomly picks an edge out of the given state, if
+ one exists. It updates self.edge if a transition has
+ been selected, or returns None if none can be found. It also
+ updates self.image to be the new image on the selected edge.
+ """
+
+ if state not in self.edges:
+ self.edge = None
+ return
+
+ edges = self.edges[state]
+ self.edge = random.choice(edges)
+ self.state = self.edge.new
+
+ def update_cache(self):
+ """
+ Places the correct Displayable into the edge cache, based on
+ what is contained in the given edge. This takes into account
+ the old and new states, and any transition that is present.
+ """
+
+
+ if self.edge.trans:
+ im = self.edge.trans(old_widget=self.states[self.edge.old].get_image(),
+ new_widget=self.states[self.edge.new].get_image())
+ elif self.showold:
+ im = self.states[self.edge.old].get_image()
+ else:
+ im = self.states[self.edge.new].get_image()
+
+ self.edge_cache = im
+
+ def get_placement(self):
+
+ if self.edge_cache:
+ return self.edge_cache.get_placement()
+
+ if self.state:
+ return self.states[self.state].get_image().get_placement()
+
+ return super(SMAnimation, self).get_placement()
+
+ def render(self, width, height, st, at):
+
+ if self.anim_timebase:
+ t = at
+ else:
+ t = st
+
+ if self.edge_start is None or t < self.edge_start:
+ self.edge_start = t
+ self.edge_cache = None
+ self.pick_edge(self.initial)
+
+ while self.edge and t > self.edge_start + self.edge.delay:
+ self.edge_start += self.edge.delay
+ self.edge_cache = None
+ self.pick_edge(self.edge.new)
+
+ # If edge is None, then we have a permanent, static picture. Deal
+ # with that.
+
+ if not self.edge:
+ im = renpy.display.render.render(self.states[self.state].get_image(),
+ width, height,
+ st - self.edge_start, at)
+
+
+ # Otherwise, we have another edge.
+
+ else:
+ if not self.edge_cache:
+ self.update_cache()
+
+ im = renpy.display.render.render(self.edge_cache, width, height, t - self.edge_start, at)
+
+ if not renpy.game.less_updates:
+ renpy.display.render.redraw(self.edge_cache, self.edge.delay - (t - self.edge_start))
+
+
+ iw, ih = im.get_size()
+
+ rv = renpy.display.render.Render(iw, ih)
+ rv.blit(im, (0, 0))
+
+ return rv
+
+ def __call__(self, child=None, new_widget=None, old_widget=None):
+ """
+ Used when this SMAnimation is used as a SMMotion. This creates
+ a duplicate of the animation, with all states containing None
+ as the image having that None replaced with the image that is provided here.
+ """
+
+ if child is None:
+ child = new_widget
+
+ args = [ ]
+
+ for state in self.states.values():
+ args.append(state.motion_copy(child))
+
+ for edges in self.edges.values():
+ args.extend(edges)
+
+ return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
+
+
+# class Animation(renpy.display.core.Displayable):
+# """
+# A Displayable that draws an animation, which is a series of images
+# that are displayed with time delays between them.
+# """
+
+# def __init__(self, *args, **properties):
+# """
+# Odd (first, third, fifth, etc.) arguments to Animation are
+# interpreted as image filenames, while even arguments are the
+# time to delay between each image. If the number of arguments
+# is odd, the animation will stop with the last image (well,
+# actually delay for a year before looping). Otherwise, the
+# animation will restart after the final delay time.
+
+# @param anim_timebase: If True, the default, use the animation
+# timebase. Otherwise, use the displayable timebase.
+# """
+
+# properties.setdefault('style', 'animation')
+# self.anim_timebase = properties.pop('anim_timebase', True)
+
+# super(Animation, self).__init__(**properties)
+
+# self.images = [ ]
+# self.delays = [ ]
+
+# for i, arg in enumerate(args):
+
+# if i % 2 == 0:
+# self.images.append(renpy.easy.displayable(arg))
+# else:
+# self.delays.append(arg)
+
+# if len(self.images) > len(self.delays):
+# self.delays.append(365.25 * 86400.0) # One year, give or take.
+
+# def render(self, width, height, st, at):
+
+# if self.anim_timebase:
+# t = at % sum(self.delays)
+# else:
+# t = st % sum(self.delays)
+
+# for image, delay in zip(self.images, self.delays):
+# if t < delay:
+# renpy.display.render.redraw(self, delay - t)
+
+# im = renpy.display.render.render(image, width, height, t, at)
+# width, height = im.get_size()
+# rv = renpy.display.render.Render(width, height)
+# rv.blit(im, (0, 0))
+
+# return rv
+
+# else:
+# t = t - delay
+
+# def visit(self):
+# return self.images
+
+def Animation(*args, **kwargs):
+ newargs = [ ]
+
+ for i, a in enumerate(args):
+ newargs.append(a)
+ if i % 2 == 1:
+ newargs.append(None)
+
+ return TransitionAnimation(*newargs, **kwargs)
+
+
+class TransitionAnimation(renpy.display.core.Displayable):
+ """
+ A displayable that draws an animation with each frame separated
+ by a transition.
+ """
+
+ def __init__(self, *args, **properties):
+ """
+ This takes arguments such that the 1st, 4th, 7th, ...
+ arguments are displayables, the 2nd, 5th, 8th, ... on arguments
+ are times, and the 3rd, 6th, 9th, ... are transitions.
+
+ This displays the first displayable for the given time, then
+ transitions to the second displayable using the given
+ transition, and shows it for the given time (the time of the
+ transition is taken out of the time the frame is shown), and
+ so on.
+
+ The last argument may be a displayable (in which case that
+ displayable is used to transition back to the first frame), or
+ a displayable (which is shown forever).
+
+ There is one keyword argument, apart from the style properties:
+
+ @param anim_timebase: If True, the default, use the animation
+ timebase. Otherwise, use the displayable timebase.
+ """
+
+ properties.setdefault('style', 'animation')
+ self.anim_timebase = properties.pop('anim_timebase', True)
+
+ super(TransitionAnimation, self).__init__(**properties)
+
+ images = [ ]
+ delays = [ ]
+ transitions = [ ]
+
+ for i, arg in enumerate(args):
+
+ if i % 3 == 0:
+ images.append(renpy.easy.displayable(arg))
+ elif i % 3 == 1:
+ delays.append(arg)
+ else:
+ transitions.append(arg)
+
+ if len(images) > len(delays):
+ delays.append(365.25 * 86400.0) # One year, give or take.
+ if len(images) > len(transitions):
+ transitions.append(None)
+
+ self.images = images
+ self.prev_images = [ images[-1] ] + images[:-1]
+ self.delays = delays
+ self.transitions = [ transitions[-1] ] + transitions[:-1]
+
+
+ def render(self, width, height, st, at):
+
+ if self.anim_timebase:
+ orig_t = at
+ else:
+ orig_t = st
+
+ t = orig_t % sum(self.delays)
+
+ for image, prev, delay, trans in zip(self.images, self.prev_images, self.delays, self.transitions):
+ if t < delay:
+ if not renpy.game.less_updates:
+ renpy.display.render.redraw(self, delay - t)
+
+ if trans and orig_t >= self.delays[0]:
+ image = trans(old_widget=prev, new_widget=image)
+
+ im = renpy.display.render.render(image, width, height, t, at)
+ width, height = im.get_size()
+ rv = renpy.display.render.Render(width, height)
+ rv.blit(im, (0, 0))
+
+ return rv
+
+ else:
+ t = t - delay
+
+ def visit(self):
+ return self.images
+
+class Blink(renpy.display.core.Displayable):
+ """
+ """
+
+ def __init__(self, image, on=0.5, off=0.5, rise=0.5, set=0.5, #@ReservedAssignment
+ high=1.0, low=0.0, offset=0.0, anim_timebase=False, **properties):
+
+ """
+ This takes as an argument an image or widget, and blinks that image
+ by varying its alpha. The sequence of phases is
+ on - set - off - rise - on - ... All times are given in seconds, all
+ alphas are fractions between 0 and 1.
+
+ @param image: The image or widget that will be blinked.
+
+ @param on: The amount of time the widget spends on, at high alpha.
+
+ @param off: The amount of time the widget spends off, at low alpha.
+
+ @param rise: The amount time the widget takes to ramp from low to high alpha.
+
+ @param set: The amount of time the widget takes to ram from high to low.
+
+ @param high: The high alpha.
+
+ @param low: The low alpha.
+
+ @param offset: A time offset, in seconds. Use this to have a
+ blink that does not start at the start of the on phase.
+
+ @param anim_timebase: If True, use the animation timebase, if false, the displayable timebase.
+ """
+
+ super(Blink, self).__init__(**properties)
+
+ self.image = renpy.easy.displayable(image)
+ self.on = on
+ self.off = off
+ self.rise = rise
+ self.set = set
+ self.high = high
+ self.low = low
+ self.offset = offset
+ self.anim_timebase = anim_timebase
+
+ self.cycle = on + set + off + rise
+
+
+ def visit(self):
+ return [ self.image ]
+
+ def render(self, height, width, st, at):
+
+ if self.anim_timebase:
+ t = at
+ else:
+ t = st
+
+ time = (self.offset + t) % self.cycle
+ alpha = self.high
+
+ if 0 <= time < self.on:
+ delay = self.on - time
+ alpha = self.high
+
+ time -= self.on
+
+ if 0 <= time < self.set:
+ delay = 0
+ frac = time / self.set
+ alpha = self.low * frac + self.high * (1.0 - frac)
+
+ time -= self.set
+
+ if 0 <= time < self.off:
+ delay = self.off - time
+ alpha = self.low
+
+ time -= self.off
+
+ if 0 <= time < self.rise:
+ delay = 0
+ frac = time / self.rise
+ alpha = self.high * frac + self.low * (1.0 - frac)
+
+
+ rend = renpy.display.render.render(self.image, height, width, st, at)
+ w, h = rend.get_size()
+ rv = renpy.display.render.Render(w, h)
+
+ rv.blit(rend, (0, 0))
+ rv.alpha = alpha
+
+ if not renpy.game.less_updates:
+ renpy.display.render.redraw(self, delay)
+
+ return rv
+
+
+
+def Filmstrip(image, framesize, gridsize, delay, frames=None, loop=True, **properties):
+ """
+ This creates an animation from a single image. This image
+ must consist of a grid of frames, with the number of columns and
+ rows in the grid being taken from gridsize, and the size of each
+ frame in the grid being taken from framesize. This takes frames
+ and sticks them into an Animation, with the given delay between
+ each frame. The frames are taken by going from left-to-right
+ across the first row, left-to-right across the second row, and
+ so on until all frames are consumed, or a specified number of
+ frames are taken.
+
+ @param image: The image that the frames must be taken from.
+
+ @param framesize: A (width, height) tuple giving the size of
+ each of the frames in the animation.
+
+ @param gridsize: A (columns, rows) tuple giving the number of
+ columns and rows in the grid.
+
+ @param delay: The delay, in seconds, between frames.
+
+ @param frames: The number of frames in this animation. If None,
+ then this defaults to colums * rows frames, that is, taking
+ every frame in the grid.
+
+ @param loop: If True, loop at the end of the animation. If False,
+ this performs the animation once, and then stops.
+
+ Other keyword arguments are as for anim.SMAnimation.
+ """
+
+ width, height = framesize
+ cols, rows = gridsize
+
+ if frames is None:
+ frames = cols * rows
+
+ i = 0
+
+ # Arguments to Animation
+ args = [ ]
+
+ for r in range(0, rows):
+ for c in range(0, cols):
+
+ x = c * width
+ y = r * height
+
+ args.append(renpy.display.im.Crop(image, x, y, width, height))
+ args.append(delay)
+
+ i += 1
+ if i == frames:
+ break
+
+ if i == frames:
+ break
+
+ if not loop:
+ args.pop()
+
+ return Animation(*args, **properties)
diff --git a/unrpyc/renpy/display/behavior.py b/unrpyc/renpy/display/behavior.py
new file mode 100644
index 0000000..02eccf2
--- /dev/null
+++ b/unrpyc/renpy/display/behavior.py
@@ -0,0 +1,1531 @@
+# 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 contains various Displayables that handle events.
+
+
+import renpy.display
+import renpy.audio
+
+from renpy.display.render import render, Render
+
+import pygame
+
+def compile_event(key, keydown):
+ """
+ Compiles a keymap entry into a python expression.
+
+ keydown determines if we are dealing with keys going down (press),
+ or keys going up (release).
+ """
+
+ # Lists or tuples get turned into or expressions.
+ if isinstance(key, (list, tuple)):
+ if not key:
+ return "(False)"
+
+ return "(" + " or ".join([compile_event(i, keydown) for i in key]) + ")"
+
+ # If it's in config.keymap, compile what's in config.keymap.
+ if key in renpy.config.keymap:
+ return compile_event(renpy.config.keymap[key], keydown)
+
+ if key is None:
+ return "(False)"
+
+ part = key.split("_")
+
+ # Deal with the mouse.
+ if part[0] == "mousedown":
+ if keydown:
+ return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONDOWN, int(part[1]))
+ else:
+ return "(False)"
+
+ if part[0] == "mouseup":
+ if keydown:
+ return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONUP, int(part[1]))
+ else:
+ return "(False)"
+
+ # Deal with the Joystick.
+ if part[0] == "joy":
+ if keydown:
+ return "(ev.type == %d and ev.press and ev.press == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+ else:
+ return "(ev.type == %d and ev.release and ev.release == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+
+ # Otherwise, deal with it as a key.
+ if keydown:
+ rv = "(ev.type == %d" % pygame.KEYDOWN
+ else:
+ rv = "(ev.type == %d" % pygame.KEYUP
+
+ if part[0] == "alt":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_ALT
+ else:
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_ALT
+
+ if part[0] == "meta":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_META
+ else:
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_META
+
+ if part[0] == "shift":
+ part.pop(0)
+ rv += " and (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+ if part[0] == "noshift":
+ part.pop(0)
+ rv += " and not (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+ if len(part) == 1:
+ if len(part[0]) != 1:
+ if renpy.config.developer:
+ raise Exception("Invalid key specifier %s" % key)
+ else:
+ return "(False)"
+
+ rv += " and ev.unicode == %r)" % part[0]
+
+ else:
+ if part[0] != "K":
+ if renpy.config.developer:
+ raise Exception("Invalid key specifier %s" % key)
+ else:
+ return "(False)"
+
+ key = "_".join(part)
+
+ rv += " and ev.key == %d)" % (getattr(pygame.constants, key))
+
+ return rv
+
+# These store a lambda for each compiled key in the system.
+event_cache = { }
+keyup_cache = { }
+
+def map_event(ev, name):
+ """Returns true if the event matches the named keycode being pressed."""
+
+ check_code = event_cache.get(name, None)
+ if check_code is None:
+ check_code = eval("lambda ev : " + compile_event(name, True), globals())
+ event_cache[name] = check_code
+
+ return check_code(ev)
+
+def map_keyup(ev, name):
+ """Returns true if the event matches the named keycode being released."""
+
+ check_code = keyup_cache.get(name, None)
+ if check_code is None:
+ check_code = eval("lambda ev : " + compile_event(name, False), globals())
+ keyup_cache[name] = check_code
+
+ return check_code(ev)
+
+
+def skipping(ev):
+ """
+ This handles setting skipping in response to the press of one of the
+ CONTROL keys. The library handles skipping in response to TAB.
+ """
+
+ if not renpy.config.allow_skipping:
+ return
+
+ if map_event(ev, "skip"):
+ renpy.config.skipping = "slow"
+ renpy.exports.restart_interaction()
+
+ if map_keyup(ev, "skip"):
+ renpy.config.skipping = None
+ renpy.exports.restart_interaction()
+
+ return
+
+
+def inspector(ev):
+ return map_event(ev, "inspector")
+
+
+##############################################################################
+# Utility functions for dealing with actions.
+
+def predict_action(var):
+ """
+ Predicts some of the actions that may be caused by a variable.
+ """
+
+ if var is None:
+ return
+
+ if isinstance(var, renpy.ui.Action):
+ var.predict()
+
+ if isinstance(var, (list, tuple)):
+ for i in var:
+ predict_action(i)
+
+def run(var, *args, **kwargs):
+ """
+ Runs a variable. This is done by calling all the functions, and
+ iterating over the lists and tuples.
+ """
+
+ if var is None:
+ return None
+
+ if isinstance(var, (list, tuple)):
+ rv = None
+
+ for i in var:
+ new_rv = run(i, *args, **kwargs)
+
+ if new_rv is not None:
+ rv = new_rv
+
+ return rv
+
+ return var(*args, **kwargs)
+
+def run_unhovered(var):
+ """
+ Calls the unhovered method on the variable, if it exists.
+ """
+
+ if var is None:
+ return None
+
+ if isinstance(var, (list, tuple)):
+ for i in var:
+ run_unhovered(i)
+
+ return
+
+ f = getattr(var, "unhovered", None)
+ if f is not None:
+ f()
+
+def run_periodic(var, st):
+
+ if isinstance(var, (list, tuple)):
+ rv = None
+
+ for i in var:
+ v = run_periodic(i, st)
+
+ if rv is None or v < rv:
+ rv = v
+
+ return rv
+
+ if isinstance(var, renpy.ui.Action):
+ return var.periodic(st)
+
+
+def is_selected(clicked):
+
+ if isinstance(clicked, (list, tuple)):
+ return any(is_selected(i) for i in clicked)
+
+ elif isinstance(clicked, renpy.ui.Action):
+ return clicked.get_selected()
+ else:
+ return False
+
+
+def is_sensitive(clicked):
+
+ if isinstance(clicked, (list, tuple)):
+ return all(is_sensitive(i) for i in clicked)
+
+ elif isinstance(clicked, renpy.ui.Action):
+ return clicked.get_sensitive()
+ else:
+ return True
+
+
+##############################################################################
+# Special-Purpose Displayables
+
+class Keymap(renpy.display.layout.Null):
+ """
+ This is a behavior that maps keys to actions that are called when
+ the key is pressed. The keys are specified by giving the appropriate
+ k_constant from pygame.constants, or the unicode for the key.
+ """
+
+ def __init__(self, replaces=None, **keymap):
+ super(Keymap, self).__init__(style='default')
+ self.keymap = keymap
+
+ def event(self, ev, x, y, st):
+
+ for name, action in self.keymap.items():
+ if map_event(ev, name):
+
+ rv = run(action)
+
+ if rv is not None:
+ return rv
+
+ raise renpy.display.core.IgnoreEvent()
+
+ def predict_one_action(self):
+ for i in self.keymap.values():
+ predict_action(i)
+
+
+class RollForward(renpy.display.layout.Null):
+ """
+ This behavior implements rollforward.
+ """
+
+ def __init__(self, value, **properties):
+ super(RollForward, self).__init__(**properties)
+ self.value = value
+
+
+ def event(self, ev, x, y, st):
+
+ if map_event(ev, "rollforward"):
+ renpy.game.interface.suppress_transition = True
+ renpy.game.after_rollback = True
+ renpy.game.log.rolled_forward = True
+ return self.value
+
+
+class PauseBehavior(renpy.display.layout.Null):
+ """
+ This is a class implementing the Pause behavior, which is to
+ return a value after a certain amount of time has elapsed.
+ """
+
+ def __init__(self, delay, result=False, **properties):
+ super(PauseBehavior, self).__init__(**properties)
+
+ self.delay = delay
+ self.result = result
+
+ def event(self, ev, x, y, st):
+
+ if st >= self.delay:
+
+ # If we have been drawn since the timeout, simply return
+ # true. Otherwise, force a redraw, and return true when
+ # it comes back.
+ if renpy.game.interface.drawn_since(st - self.delay):
+ return self.result
+ else:
+ renpy.game.interface.force_redraw = True
+
+
+ renpy.game.interface.timeout(max(self.delay - st, 0))
+
+class SoundStopBehavior(renpy.display.layout.Null):
+ """
+ This is a class implementing the sound stop behavior,
+ which is to return False when a sound is no longer playing
+ on the named channel.
+ """
+
+ def __init__(self, channel, result=False, **properties):
+ super(SoundStopBehavior, self).__init__(**properties)
+
+ self.channel = channel
+ self.result = result
+
+
+ def event(self, ev, x, y, st):
+
+ if not renpy.audio.music.get_playing(self.channel):
+ return self.result
+
+ renpy.game.interface.timeout(.025)
+
+
+class SayBehavior(renpy.display.layout.Null):
+ """
+ This is a class that implements the say behavior,
+ which is to return True (ending the interaction) if
+ the user presses space or enter, or clicks the left
+ mouse button.
+ """
+
+ focusable = True
+
+ def __init__(self, default=True, afm=None, dismiss=[ 'dismiss' ], allow_dismiss=None, **properties):
+ super(SayBehavior, self).__init__(default=default, **properties)
+
+ if not isinstance(dismiss, (list, tuple)):
+ dismiss = [ dismiss ]
+
+ if afm is not None:
+ self.afm_length = len(afm)
+ else:
+ self.afm_length = None
+
+ # What keybindings lead to dismissal?
+ self.dismiss = dismiss
+
+ self.allow_dismiss = allow_dismiss
+
+ def set_afm_length(self, afm_length):
+ self.afm_length = max(afm_length, 1)
+
+ def event(self, ev, x, y, st):
+
+ if self.afm_length and renpy.game.preferences.afm_time and renpy.game.preferences.afm_enable:
+
+ afm_delay = ( 1.0 * ( renpy.config.afm_bonus + self.afm_length ) / renpy.config.afm_characters ) * renpy.game.preferences.afm_time
+
+ if renpy.game.preferences.text_cps:
+ afm_delay += 1.0 / renpy.game.preferences.text_cps * self.afm_length
+
+ if st > afm_delay:
+ if renpy.config.afm_callback:
+ if renpy.config.afm_callback():
+ return True
+ else:
+ renpy.game.interface.timeout(0.1)
+ else:
+ return True
+ else:
+ renpy.game.interface.timeout(afm_delay - st)
+
+ for dismiss in self.dismiss:
+
+ if map_event(ev, dismiss) and self.is_focused():
+
+ if renpy.config.skipping:
+ renpy.config.skipping = None
+ renpy.exports.restart_interaction()
+ raise renpy.display.core.IgnoreEvent()
+
+ if renpy.game.preferences.using_afm_enable and renpy.game.preferences.afm_enable:
+ renpy.game.preferences.afm_enable = False
+ renpy.exports.restart_interaction()
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.allow_dismiss:
+ if not self.allow_dismiss():
+ raise renpy.display.core.IgnoreEvent()
+
+ return True
+
+ skip_delay = renpy.config.skip_delay / 1000.0
+
+ if renpy.config.allow_skipping and renpy.config.skipping:
+
+ if st >= skip_delay:
+ if renpy.game.preferences.skip_unseen:
+ return True
+ elif renpy.config.skipping == "fast":
+ return True
+ elif renpy.game.context().seen_current(True):
+ return True
+ else:
+ renpy.game.interface.timeout(skip_delay - st)
+
+
+ return None
+
+
+##############################################################################
+# Button
+
+class Button(renpy.display.layout.Window):
+
+ keymap = { }
+ action = None
+
+ def __init__(self, child=None, style='button', clicked=None,
+ hovered=None, unhovered=None, action=None, role=None,
+ time_policy=None, keymap={},
+ **properties):
+
+ super(Button, self).__init__(child, style=style, **properties)
+
+ if isinstance(clicked, renpy.ui.Action):
+ action = clicked
+
+ if action is not None:
+ clicked = action
+
+ if not is_sensitive(action):
+ clicked = None
+
+ if role is None:
+ if action:
+ if is_selected(action):
+ role = 'selected_'
+ else:
+ role = ''
+ else:
+ role = ''
+
+ self.action = action
+ self.activated = False
+ self.clicked = clicked
+ self.hovered = hovered
+ self.unhovered = unhovered
+ self.focusable = clicked is not None
+ self.role = role
+ self.keymap = keymap
+
+ self.time_policy_data = None
+
+ def predict_one_action(self):
+ predict_action(self.clicked)
+ predict_action(self.hovered)
+ predict_action(self.unhovered)
+
+ if self.keymap:
+ for v in self.keymap.values():
+ predict_action(v)
+
+ def render(self, width, height, st, at):
+
+ if self.style.time_policy:
+ st, self.time_policy_data = self.style.time_policy(st, self.time_policy_data, self.style)
+
+ rv = super(Button, self).render(width, height, st, at)
+
+ if self.clicked:
+
+ rect = self.style.focus_rect
+ if rect is not None:
+ fx, fy, fw, fh = rect
+ else:
+ fx = self.style.left_margin
+ fy = self.style.top_margin
+ fw = rv.width - self.style.right_margin
+ fh = rv.height - self.style.bottom_margin
+
+ mask = self.style.focus_mask
+
+ if mask is True:
+ mask = rv
+ elif mask is not None:
+ mask = renpy.easy.displayable(mask)
+ mask = renpy.display.render.render(mask, rv.width, rv.height, st, at)
+
+ if mask is not None:
+ fmx = 0
+ fmy = 0
+ else:
+ fmx = None
+ fmy = None
+
+ rv.add_focus(self, None,
+ fx, fy, fw, fh,
+ fmx, fmy, mask)
+
+ return rv
+
+
+ def focus(self, default=False):
+ super(Button, self).focus(default)
+
+ if self.activated:
+ return None
+
+ rv = None
+
+ if not default:
+ rv = run(self.hovered)
+
+ self.set_transform_event(self.role + "hover")
+ self.child.set_transform_event(self.role + "hover")
+
+ return rv
+
+
+ def unfocus(self, default=False):
+ super(Button, self).unfocus(default)
+
+ if self.activated:
+ return None
+
+ if not default:
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+ self.set_transform_event(self.role + "idle")
+ self.child.set_transform_event(self.role + "idle")
+
+
+ def per_interact(self):
+ if not self.clicked:
+ self.set_style_prefix(self.role + "insensitive_", True)
+ else:
+ self.set_style_prefix(self.role + "idle_", True)
+
+ super(Button, self).per_interact()
+
+ def event(self, ev, x, y, st):
+
+ # Call self.action.periodic()
+ timeout = run_periodic(self.action, st)
+
+ if timeout is not None:
+ renpy.game.interface.timeout(timeout)
+
+ # If we have a child, try passing the event to it. (For keyboard
+ # events, this only happens if we're focused.)
+ if self.is_focused() or not (ev.type == pygame.KEYDOWN or ev.type == pygame.KEYUP):
+ rv = super(Button, self).event(ev, x, y, st)
+ if rv is not None:
+ return rv
+
+ # If not focused, ignore all events.
+ if not self.is_focused():
+ return None
+
+ # Check the keymap.
+ for name, action in self.keymap.items():
+ if map_event(ev, name):
+ return run(action)
+
+ # Ignore as appropriate:
+ if map_event(ev, "button_ignore") and self.clicked:
+ raise renpy.display.core.IgnoreEvent()
+
+ # If clicked,
+ if map_event(ev, "button_select") and self.clicked:
+
+ self.activated = True
+ self.style.set_prefix(self.role + 'activate_')
+
+ if self.style.sound:
+ renpy.audio.music.play(self.style.sound, channel="sound")
+
+ rv = run(self.clicked)
+
+ if rv is not None:
+ return rv
+ else:
+ self.activated = False
+
+ if self.is_focused():
+ self.set_style_prefix(self.role + "hover_", True)
+ else:
+ self.set_style_prefix(self.role + "idle_", True)
+
+ raise renpy.display.core.IgnoreEvent()
+
+ return None
+
+
+ def set_style_prefix(self, prefix, root):
+ if root:
+ super(Button, self).set_style_prefix(prefix, root)
+
+
+# Reimplementation of the TextButton widget as a Button and a Text
+# widget.
+def TextButton(text, style='button', text_style='button_text',
+ clicked=None, **properties):
+
+ text = renpy.text.text.Text(text, style=text_style) #@UndefinedVariable
+ return Button(text, style=style, clicked=clicked, **properties)
+
+class ImageButton(Button):
+ """
+ Used to implement the guts of an image button.
+ """
+
+ def __init__(self,
+ idle_image,
+ hover_image,
+ insensitive_image = None,
+ activate_image = None,
+ selected_idle_image = None,
+ selected_hover_image = None,
+ selected_insensitive_image = None,
+ selected_activate_image = None,
+ style='image_button',
+ clicked=None,
+ hovered=None,
+ **properties):
+
+ insensitive_image = insensitive_image or idle_image
+ activate_image = activate_image or hover_image
+
+ selected_idle_image = selected_idle_image or idle_image
+ selected_hover_image = selected_hover_image or hover_image
+ selected_insensitive_image = selected_insensitive_image or insensitive_image
+ selected_activate_image = selected_activate_image or activate_image
+
+ self.state_children = dict(
+ idle_ = renpy.easy.displayable(idle_image),
+ hover_ = renpy.easy.displayable(hover_image),
+ insensitive_ = renpy.easy.displayable(insensitive_image),
+ activate_ = renpy.easy.displayable(activate_image),
+
+ selected_idle_ = renpy.easy.displayable(selected_idle_image),
+ selected_hover_ = renpy.easy.displayable(selected_hover_image),
+ selected_insensitive_ = renpy.easy.displayable(selected_insensitive_image),
+ selected_activate_ = renpy.easy.displayable(selected_activate_image),
+ )
+
+ super(ImageButton, self).__init__(renpy.display.layout.Null(),
+ style=style,
+ clicked=clicked,
+ hovered=hovered,
+ **properties)
+
+ def visit(self):
+ return list(self.state_children.values())
+
+ def get_child(self):
+ return self.style.child or self.state_children[self.style.prefix]
+
+
+# This is used for an input that takes its focus from a button.
+class HoveredProxy(object):
+ def __init__(self, a, b):
+ self.a = a
+ self.b = b
+
+ def __call__(self):
+ self.a()
+ if self.b:
+ return self.b()
+
+
+class Input(renpy.text.text.Text): #@UndefinedVariable
+ """
+ This is a Displayable that takes text as input.
+ """
+
+ changed = None
+ prefix = ""
+ suffix = ""
+ caret_pos = 0
+
+ def __init__(self,
+ default="",
+ length=None,
+ style='input',
+ allow=None,
+ exclude=None,
+ prefix="",
+ suffix="",
+ changed=None,
+ button=None,
+ replaces=None,
+ editable=True,
+ **properties):
+
+ super(Input, self).__init__("", style=style, replaces=replaces, substitute=False, **properties)
+
+ self.content = str(default)
+ self.length = length
+
+ self.allow = allow
+ self.exclude = exclude
+ self.prefix = prefix
+ self.suffix = suffix
+
+ self.changed = changed
+
+ self.editable = editable
+
+ caretprops = { 'color' : None }
+
+ for i in properties:
+ if i.endswith("color"):
+ caretprops[i] = properties[i]
+
+ self.caret = renpy.display.image.Solid(xmaximum=1, style=style, **caretprops)
+ self.caret_pos = len(self.content)
+
+ if button:
+ self.editable = False
+ button.hovered = HoveredProxy(self.enable, button.hovered)
+ button.unhovered = HoveredProxy(self.disable, button.unhovered)
+
+ if isinstance(replaces, Input):
+ self.content = replaces.content
+ self.editable = replaces.editable
+ self.caret_pos = replaces.caret_pos
+
+ self.update_text(self.content, self.editable)
+
+
+ def update_text(self, content, editable):
+
+ if content != self.content or editable != self.editable:
+ renpy.display.render.redraw(self, 0)
+
+ if content != self.content:
+ self.content = content
+
+ if self.changed:
+ self.changed(content)
+
+ if content == "":
+ content = "\u200b"
+
+ self.editable = editable
+
+ # Choose the caret.
+ caret = self.style.caret
+ if caret is None:
+ caret = self.caret
+
+ if editable:
+ l = len(content)
+ self.set_text([self.prefix, content[0:self.caret_pos].replace("{", "{{"), caret,
+ content[self.caret_pos:l].replace("{", "{{"), self.suffix])
+ else:
+ self.set_text([self.prefix, content.replace("{", "{{"), self.suffix ])
+
+ # This is needed to ensure the caret updates properly.
+ def set_style_prefix(self, prefix, root):
+ if prefix != self.style.prefix:
+ self.update_text(self.content, self.editable)
+
+ super(Input, self).set_style_prefix(prefix, root)
+
+ def enable(self):
+ self.update_text(self.content, True)
+
+ def disable(self):
+ self.update_text(self.content, False)
+
+ def event(self, ev, x, y, st):
+
+ if not self.editable:
+ return None
+
+ l = len(self.content)
+
+ if map_event(ev, "input_backspace"):
+
+ if self.content and self.caret_pos > 0:
+ content = self.content[0:self.caret_pos-1] + self.content[self.caret_pos:l]
+ self.caret_pos -= 1
+ self.update_text(content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_enter"):
+ if not self.changed:
+ return self.content
+
+ elif map_event(ev, "input_left"):
+ if self.caret_pos > 0:
+ self.caret_pos -= 1
+ self.update_text(self.content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_right"):
+ if self.caret_pos < l:
+ self.caret_pos += 1
+ self.update_text(self.content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif map_event(ev, "input_delete"):
+ if self.caret_pos < l:
+ content = self.content[0:self.caret_pos] + self.content[self.caret_pos+1:l]
+ self.update_text(content, self.editable)
+
+ renpy.display.render.redraw(self, 0)
+ raise renpy.display.core.IgnoreEvent()
+
+ elif ev.type == pygame.KEYDOWN and ev.str:
+ if ord(ev.str[0]) < 32:
+ return None
+
+ if self.length and len(self.content) >= self.length:
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.allow and ev.str not in self.allow:
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.exclude and ev.str in self.exclude:
+ raise renpy.display.core.IgnoreEvent()
+
+ content = self.content[0:self.caret_pos] + ev.str + self.content[self.caret_pos:l]
+ self.caret_pos += 1
+
+ self.update_text(content, self.editable)
+
+ raise renpy.display.core.IgnoreEvent()
+
+# A map from adjustment to lists of displayables that want to be redrawn
+# if the adjustment changes.
+adj_registered = { }
+
+# This class contains information about an adjustment that can change the
+# position of content.
+class Adjustment(renpy.object.Object):
+ """
+ :doc: ui
+ :name: ui.adjustment class
+
+ Adjustment objects represent a value that can be adjusted by a bar
+ or viewport. They contain information about the value, the range
+ of the value, and how to adjust the value in small steps and large
+ pages.
+
+
+ """
+
+ def __init__(self, range=1, value=0, step=None, page=0, changed=None, adjustable=None, ranged=None): #@ReservedAssignment
+ """
+ The following parameters correspond to fields or properties on
+ the adjustment object:
+
+ `range`
+ The range of the adjustment, a number.
+
+ `value`
+ The value of the adjustment, a number.
+
+ `step`
+ The step size of the adjustment, a number. If None, then
+ defaults to 1/10th of a page, if set. Otherwise, defaults
+ to the 1/20th of the range.
+
+ This is used when scrolling a viewport with the mouse wheel.
+
+ `page`
+ The page size of the adjustment. If None, this is set
+ automatically by a viewport. If never set, defaults to 1/10th
+ of the range.
+
+ It's can be used when clicking on a scrollbar.
+
+ The following parameters control the behavior of the adjustment.
+
+ `adjustable`
+ If True, this adjustment can be changed by a bar. If False,
+ it can't.
+
+ It defaults to being adjustable if a `changed` function
+ is given or if the adjustment is associated with a viewport,
+ and not adjustable otherwise.
+
+ `changed`
+ This function is called with the new value when the value of
+ the adjustment changes.
+
+ `ranged`
+ This function is called with the adjustment object when
+ the range of the adjustment is set by a viewport.
+
+ .. method:: change(value)
+
+ Changes the value of the adjustment to `value`, updating
+ any bars and viewports that use the adjustment.
+ """
+
+
+ super(Adjustment, self).__init__()
+
+ if adjustable is None:
+ if changed:
+ adjustable = True
+
+ self._value = value
+ self._range = range
+ self._page = page
+ self._step = step
+ self.changed = changed
+ self.adjustable = adjustable
+ self.ranged = ranged
+
+ def get_value(self):
+ if self._value > self._range:
+ return self._range
+
+ return self._value
+
+ def set_value(self, v):
+ self._value = v
+
+ value = property(get_value, set_value)
+
+ def get_range(self):
+ return self._range
+
+ def set_range(self, v):
+ self._range = v
+ if self.ranged:
+ self.ranged(self)
+
+ range = property(get_range, set_range) #@ReservedAssignment
+
+ def get_page(self):
+ if self._page is not None:
+ return self._page
+
+ return self._range / 10
+
+ def set_page(self, v):
+ self._page = v
+
+ page = property(get_page, set_page)
+
+ def get_step(self):
+ if self._step is not None:
+ return self._step
+
+ if self._page is not None and self.page > 0:
+ return self._page / 10
+
+ if isinstance(self._range, float):
+ return self._range / 10
+ else:
+ return 1
+
+ def set_step(self, v):
+ self._step = v
+
+ step = property(get_step, set_step)
+
+ # Register a displayable to be redrawn when this adjustment changes.
+ def register(self, d):
+ adj_registered.setdefault(self, [ ]).append(d)
+
+ def change(self, value):
+
+ if value < 0:
+ value = 0
+ if value > self._range:
+ value = self._range
+
+ if value != self._value:
+ self._value = value
+ for d in adj_registered.setdefault(self, [ ]):
+ renpy.display.render.redraw(d, 0)
+ if self.changed:
+ return self.changed(value)
+
+ return None
+
+class Bar(renpy.display.core.Displayable):
+ """
+ Implements a bar that can display an integer value, and respond
+ to clicks on that value.
+ """
+
+ __version__ = 2
+
+ def after_upgrade(self, version):
+
+ if version < 1:
+ self.adjustment = Adjustment(self.range, self.value, changed=self.changed) # E1101
+ self.adjustment.register(self)
+ del self.range # E1101
+ del self.value # E1101
+ del self.changed # E1101
+
+ if version < 2:
+ self.value = None
+
+ def __init__(self,
+ range=None, #@ReservedAssignment
+ value=None,
+ width=None,
+ height=None,
+ changed=None,
+ adjustment=None,
+ step=None,
+ page=None,
+ bar=None,
+ style=None,
+ vertical=False,
+ replaces=None,
+ hovered=None,
+ unhovered=None,
+ **properties):
+
+ self.value = None
+
+ if adjustment is None:
+ if isinstance(value, renpy.ui.BarValue):
+
+ if isinstance(replaces, Bar):
+ value.replaces(replaces.value)
+
+ self.value = value
+ adjustment = value.get_adjustment()
+ renpy.game.interface.timeout(0)
+ else:
+ adjustment = Adjustment(range, value, step=step, page=page, changed=changed)
+
+ if style is None:
+ if self.value is not None:
+ if vertical:
+ style = self.value.get_style()[1]
+ else:
+ style = self.value.get_style()[0]
+ else:
+ if vertical:
+ style = 'vbar'
+ else:
+ style = 'bar'
+
+ if width is not None:
+ properties['xmaximum'] = width
+
+ if height is not None:
+ properties['ymaximum'] = height
+
+ super(Bar, self).__init__(style=style, **properties)
+
+ self.adjustment = adjustment
+ self.focusable = True
+
+ # These are set when we are first rendered.
+ self.thumb_dim = 0
+ self.height = 0
+ self.width = 0
+ self.hidden = False
+
+ self.hovered = hovered
+ self.unhovered = unhovered
+
+ def per_interact(self):
+ self.focusable = self.adjustment.adjustable
+ self.adjustment.register(self)
+
+ def predict_one(self):
+ pd = renpy.display.predict.displayable
+ style = self.style
+
+ pd(style.insensitive_fore_bar)
+ pd(style.idle_fore_bar)
+ pd(style.hover_fore_bar)
+ pd(style.selected_idle_fore_bar)
+ pd(style.selected_hover_fore_bar)
+
+ pd(style.insensitive_aft_bar)
+ pd(style.idle_aft_bar)
+ pd(style.hover_aft_bar)
+ pd(style.selected_idle_aft_bar)
+ pd(style.selected_hover_aft_bar)
+
+ pd(style.insensitive_thumb)
+ pd(style.idle_thumb)
+ pd(style.hover_thumb)
+ pd(style.selected_idle_thumb)
+ pd(style.selected_hover_thumb)
+
+ pd(style.insensitive_thumb_shadow)
+ pd(style.idle_thumb_shadow)
+ pd(style.hover_thumb_shadow)
+ pd(style.selected_idle_thumb_shadow)
+ pd(style.selected_hover_thumb_shadow)
+
+ def render(self, width, height, st, at):
+
+ # Handle redrawing.
+ if self.value is not None:
+ redraw = self.value.periodic(st)
+
+ if redraw is not None:
+ renpy.display.render.redraw(self, redraw)
+
+ # Store the width and height for the event function to use.
+ self.width = width
+ self.height = height
+ range = self.adjustment.range #@ReservedAssignment
+ value = self.adjustment.value
+ page = self.adjustment.page
+
+ if range <= 0:
+ if self.style.unscrollable == "hide":
+ self.hidden = True
+ return renpy.display.render.Render(width, height)
+ elif self.style.unscrollable == "insensitive":
+ self.set_style_prefix("insensitive_", True)
+
+ self.hidden = False
+
+ if self.style.bar_invert ^ self.style.bar_vertical:
+ value = range - value
+
+ bar_vertical = self.style.bar_vertical
+
+ if bar_vertical:
+ dimension = height
+ else:
+ dimension = width
+
+ fore_gutter = self.style.fore_gutter
+ aft_gutter = self.style.aft_gutter
+
+ active = dimension - fore_gutter - aft_gutter
+ if range:
+ thumb_dim = active * page / (range + page)
+ else:
+ thumb_dim = active
+
+ thumb_offset = abs(self.style.thumb_offset)
+
+ if bar_vertical:
+ thumb = render(self.style.thumb, width, thumb_dim, st, at)
+ thumb_shadow = render(self.style.thumb_shadow, width, thumb_dim, st, at)
+ thumb_dim = thumb.height
+ else:
+ thumb = render(self.style.thumb, thumb_dim, height, st, at)
+ thumb_shadow = render(self.style.thumb_shadow, thumb_dim, height, st, at)
+ thumb_dim = thumb.width
+
+ # Remove the offset from the thumb.
+ thumb_dim -= thumb_offset * 2
+ self.thumb_dim = thumb_dim
+
+ active -= thumb_dim
+
+ if range:
+ fore_size = active * value / range
+ else:
+ fore_size = active
+
+ fore_size = int(fore_size)
+
+ aft_size = active - fore_size
+
+ fore_size += fore_gutter
+ aft_size += aft_gutter
+
+ rv = renpy.display.render.Render(width, height)
+
+ if bar_vertical:
+
+ if self.style.bar_resizing:
+ foresurf = render(self.style.fore_bar, width, fore_size, st, at)
+ aftsurf = render(self.style.aft_bar, width, aft_size, st, at)
+ rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+ rv.blit(foresurf, (0, 0), main=False)
+ rv.blit(aftsurf, (0, height-aft_size), main=False)
+ rv.blit(thumb, (0, fore_size - thumb_offset))
+
+ else:
+ foresurf = render(self.style.fore_bar, width, height, st, at)
+ aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+ rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+ rv.blit(foresurf.subsurface((0, 0, width, fore_size)), (0, 0), main=False)
+ rv.blit(aftsurf.subsurface((0, height - aft_size, width, aft_size)), (0, height - aft_size), main=False)
+ rv.blit(thumb, (0, fore_size - thumb_offset))
+
+ else:
+ if self.style.bar_resizing:
+ foresurf = render(self.style.fore_bar, fore_size, height, st, at)
+ aftsurf = render(self.style.aft_bar, aft_size, height, st, at)
+ rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+ rv.blit(foresurf, (0, 0), main=False)
+ rv.blit(aftsurf, (width-aft_size, 0), main=False)
+ rv.blit(thumb, (fore_size - thumb_offset, 0))
+
+ else:
+ foresurf = render(self.style.fore_bar, width, height, st, at)
+ aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+ rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+ rv.blit(foresurf.subsurface((0, 0, fore_size, height)), (0, 0), main=False)
+ rv.blit(aftsurf.subsurface((width - aft_size, 0, aft_size, height)), (width-aft_size, 0), main=False)
+ rv.blit(thumb, (fore_size - thumb_offset, 0))
+
+ if self.focusable:
+ rv.add_focus(self, None, 0, 0, width, height)
+
+ return rv
+
+
+ def focus(self, default=False):
+ super(Bar, self).focus(default)
+ self.set_transform_event("hover")
+
+ if not default:
+ run(self.hovered)
+
+
+ def unfocus(self, default=False):
+ super(Bar, self).unfocus()
+ self.set_transform_event("idle")
+
+ if not default:
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+ def event(self, ev, x, y, st):
+
+ if not self.focusable:
+ return None
+
+ if not self.is_focused():
+ return None
+
+ if self.hidden:
+ return None
+
+ range = self.adjustment.range #@ReservedAssignment
+ old_value = self.adjustment.value
+ value = old_value
+
+ vertical = self.style.bar_vertical
+ invert = self.style.bar_invert ^ vertical
+ if invert:
+ value = range - value
+
+ grabbed = (renpy.display.focus.get_grab() is self)
+ just_grabbed = False
+
+ if not grabbed and map_event(ev, "bar_activate"):
+ renpy.display.focus.set_grab(self)
+ just_grabbed = True
+ grabbed = True
+
+ if grabbed:
+
+ if vertical:
+ increase = "bar_down"
+ decrease = "bar_up"
+ else:
+ increase = "bar_right"
+ decrease = "bar_left"
+
+ if map_event(ev, decrease):
+ value -= self.adjustment.step
+
+ if map_event(ev, increase):
+ value += self.adjustment.step
+
+ if ev.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+
+ if vertical:
+
+ tgutter = self.style.fore_gutter
+ bgutter = self.style.aft_gutter
+ zone_height = self.height - tgutter - bgutter - self.thumb_dim
+ if zone_height:
+ value = (y - tgutter - self.thumb_dim / 2) * range / zone_height
+ else:
+ value = 0
+
+ else:
+ lgutter = self.style.fore_gutter
+ rgutter = self.style.aft_gutter
+ zone_width = self.width - lgutter - rgutter - self.thumb_dim
+ if zone_width:
+ value = (x - lgutter - self.thumb_dim / 2) * range / zone_width
+ else:
+ value = 0
+
+ if isinstance(range, int):
+ value = int(value)
+
+ if value < 0:
+ value = 0
+
+ if value > range:
+ value = range
+
+ if invert:
+ value = range - value
+
+ if grabbed and not just_grabbed and map_event(ev, "bar_deactivate"):
+ renpy.display.focus.set_grab(None)
+
+ if value != old_value:
+ return self.adjustment.change(value)
+
+ return None
+
+
+class Conditional(renpy.display.layout.Container):
+ """
+ This class renders its child if and only if the condition is
+ true. Otherwise, it renders nothing. (Well, a Null).
+
+ Warning: the condition MUST NOT update the game state in any
+ way, as that would break rollback.
+ """
+
+ def __init__(self, condition, *args, **properties):
+ super(Conditional, self).__init__(*args, **properties)
+
+ self.condition = condition
+ self.null = renpy.display.layout.Null()
+
+ self.state = eval(self.condition, vars(renpy.store))
+
+ def render(self, width, height, st, at):
+ if self.state:
+ return render(self.child, width, height, st, at)
+ else:
+ return render(self.null, width, height, st, at)
+
+ def event(self, ev, x, y, st):
+
+ state = eval(self.condition, vars(renpy.store))
+
+ if state != self.state:
+ renpy.display.render.redraw(self, 0)
+
+ self.state = state
+
+ if state:
+ return self.child.event(ev, x, y, st)
+
+
+class TimerState(renpy.python.RevertableObject):
+ """
+ Stores the state of the timer, which may need to be rolled back.
+ """
+
+ # Prevents us from having to worry about our initialization being
+ # rolled back.
+ started = False
+ next_event = None
+
+class Timer(renpy.display.layout.Null):
+
+ __version__ = 1
+
+ started = False
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.state = TimerState()
+ self.state.started = self.started
+ self.state.next_event = self.next_event
+
+ def __init__(self, delay, action=None, repeat=False, args=(), kwargs={}, replaces=None, **properties):
+ super(Timer, self).__init__(**properties)
+
+ if action is None:
+ raise Exception("A timer must have an action supplied.")
+
+ if delay <= 0:
+ raise Exception("A timer's delay must be > 0.")
+
+ # The delay.
+ self.delay = delay
+
+ # Should we repeat the event?
+ self.repeat = repeat
+
+ # The time the next event should occur.
+ self.next_event = None
+
+ # The function and its arguments.
+ self.function = action
+ self.args = args
+ self.kwargs = kwargs
+
+ # Did we start the timer?
+ self.started = False
+
+ if replaces is not None:
+ self.state = replaces.state
+ else:
+ self.state = TimerState()
+
+
+ def event(self, ev, x, y, st):
+
+ state = self.state
+
+ if not state.started:
+ state.started = True
+ state.next_event = st + self.delay
+
+ if state.next_event is None:
+ return
+
+ if st < state.next_event:
+ renpy.game.interface.timeout(state.next_event - st)
+ return
+
+ if not self.repeat:
+ state.next_event = None
+ else:
+ state.next_event = state.next_event + self.delay
+ if state.next_event < st:
+ state.next_event = st + self.delay
+
+ renpy.game.interface.timeout(state.next_event - st)
+
+ return run(self.function, *self.args, **self.kwargs)
+
+
+class MouseArea(renpy.display.core.Displayable):
+
+ def __init__(self, hovered=None, unhovered=None, replaces=None, **properties):
+ super(MouseArea, self).__init__(**properties)
+
+ self.hovered = hovered
+ self.unhovered = unhovered
+
+ # Are we hovered right now?
+ self.is_hovered = False
+
+ if replaces is not None:
+ self.is_hovered = replaces.is_hovered
+
+ # Taken from the render.
+ self.width = 0
+ self.height = 0
+
+
+ def render(self, width, height, st, at):
+ self.width = width
+ self.height = height
+
+ return Render(width, height)
+
+ def event(self, ev, x, y, st):
+
+ if 0 <= x < self.width and 0 <= y < self.height:
+ is_hovered = True
+ else:
+ is_hovered = False
+
+ if is_hovered and not self.is_hovered:
+ self.is_hovered = True
+
+ return run(self.hovered)
+
+ elif not is_hovered and self.is_hovered:
+ self.is_hovered = False
+
+ run_unhovered(self.hovered)
+ run(self.unhovered)
+
+
diff --git a/unrpyc/renpy/display/core.py b/unrpyc/renpy/display/core.py
new file mode 100644
index 0000000..4731df0
--- /dev/null
+++ b/unrpyc/renpy/display/core.py
@@ -0,0 +1,2463 @@
+# 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 code for initializing and managing the display
+# window.
+
+import renpy.display
+import renpy.audio
+import renpy.text
+
+import pygame #@UnusedImport
+
+import sys
+import os
+import time
+import io
+import threading
+
+try:
+ import android #@UnresolvedImport @UnusedImport
+ import android.sound #@UnresolvedImport
+except:
+ android = None
+
+# Is the cpu idle enough to do other things?
+cpu_idle = threading.Event()
+cpu_idle.clear()
+
+# Need to be +4, so we don't interfere with FFMPEG's events.
+TIMEEVENT = pygame.USEREVENT + 4
+PERIODIC = pygame.USEREVENT + 5
+JOYEVENT = pygame.USEREVENT + 6
+REDRAW = pygame.USEREVENT + 7
+
+# All events except for TIMEEVENT and REDRAW
+ALL_EVENTS = [ i for i in range(0, REDRAW + 1) if i != TIMEEVENT and i != REDRAW ]
+
+# The number of msec between periodic events.
+PERIODIC_INTERVAL = 50
+
+# Time management.
+time_base = None
+
+def init_time():
+ global time_base
+ time_base = time.time() - pygame.time.get_ticks() / 1000.0
+
+def get_time():
+ return time_base + pygame.time.get_ticks() / 1000.0
+
+
+def displayable_by_tag(layer, tag):
+ """
+ Get the displayable on the given layer with the given tag.
+ """
+
+ return renpy.game.context().scene_lists.get_displayable_by_tag(layer, tag)
+
+class IgnoreEvent(Exception):
+ """
+ Exception that is raised when we want to ignore an event, but
+ also don't want to return anything.
+ """
+
+ pass
+
+class EndInteraction(Exception):
+ """
+ Exception that can be raised (for example, during the render method of
+ a displayable) to end the current interaction immediately.
+ """
+
+ def __init__(self, value):
+ self.value = value
+
+class absolute(float):
+ """
+ This represents an absolute float coordinate.
+ """
+ __slots__ = [ ]
+
+
+class Displayable(renpy.object.Object):
+ """
+ The base class for every object in Ren'Py that can be
+ displayed to the screen.
+
+ Drawables will be serialized to a savegame file. Therefore, they
+ shouldn't store non-serializable things (like pygame surfaces) in
+ their fields.
+ """
+
+ # Some invariants about method call order:
+ #
+ # per_interact is called before render.
+ # render is called before event.
+ #
+ # get_placement can be called at any time, so can't
+ # assume anything.
+
+ activated = False
+ focusable = False
+ full_focus_name = None
+ role = ''
+
+ # The event we'll pass on to our parent transform.
+ transform_event = None
+
+ # Can we change our look in response to transform_events?
+ transform_event_responder = False
+
+ def __init__(self, focus=None, default=False, style='default', **properties): # W0231
+ self.style = renpy.style.Style(style, properties, heavy=True)
+ self.focus_name = focus
+ self.default = default
+
+ def find_focusable(self, callback, focus_name):
+
+ focus_name = self.focus_name or focus_name
+
+ if self.focusable:
+ callback(self, focus_name)
+
+ for i in self.visit():
+ if i is None:
+ continue
+
+ i.find_focusable(callback, focus_name)
+
+
+ def focus(self, default=False):
+ """
+ Called to indicate that this widget has the focus.
+ """
+
+ if not self.activated:
+ self.set_style_prefix(self.role + "hover_", True)
+
+ if not default and not self.activated:
+ if self.style.sound:
+ renpy.audio.music.play(self.style.sound, channel="sound")
+
+ def unfocus(self, default=False):
+ """
+ Called to indicate that this widget has become unfocused.
+ """
+
+ if not self.activated:
+ self.set_style_prefix(self.role + "idle_", True)
+
+ def is_focused(self):
+
+ if renpy.display.focus.grab and renpy.display.focus.grab is not self:
+ return
+
+ return renpy.game.context().scene_lists.focused is self
+
+ def set_style_prefix(self, prefix, root):
+ """
+ Called to set the style prefix of this widget and its child
+ widgets, if any.
+
+ `root` - True if this is the root of a style tree, False if this
+ has been passed on to a child.
+ """
+
+ if prefix == self.style.prefix:
+ return
+
+ self.style.set_prefix(prefix)
+ renpy.display.render.redraw(self, 0)
+
+ def parameterize(self, name, parameters):
+ """
+ Called to parameterize this. By default, we don't take any
+ parameters.
+ """
+
+ if parameters:
+ raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" %
+ (' '.join(name), ' '.join(parameters)))
+
+ return self
+
+ def render(self, width, height, st, at):
+ """
+ Called to display this displayable. This is called with width
+ and height parameters, which give the largest width and height
+ that this drawable can be drawn to without overflowing some
+ bounding box. It's also given two times. It returns a Surface
+ that is the current image of this drawable.
+
+ @param st: The time since this widget was first shown, in seconds.
+ @param at: The time since a similarly named widget was first shown,
+ in seconds.
+ """
+
+ assert False, "Draw not implemented."
+
+ def event(self, ev, x, y, st):
+ """
+ Called to report than an event has occured. Ev is the raw
+ pygame event object representing that event. If the event
+ involves the mouse, x and y are the translation of the event
+ into the coordinates of this displayable. st is the time this
+ widget has been shown for.
+
+ @returns A value that should be returned from Interact, or None if
+ no value is appropriate.
+ """
+
+ return None
+
+ def get_placement(self):
+ """
+ Returns a style object containing placement information for
+ this Displayable. Children are expected to overload this
+ to return something more sensible.
+ """
+
+ return self.style.get_placement()
+
+ def visit_all(self, callback):
+ """
+ Calls the callback on this displayable and all children of this
+ displayable.
+ """
+
+ for d in self.visit():
+ if not d:
+ continue
+ d.visit_all(callback)
+
+ callback(self)
+
+ def visit(self):
+ """
+ Called to ask the displayable to return a list of its children
+ (including children taken from styles). For convenience, this
+ list may also include None values.
+ """
+
+ return [ ]
+
+ def per_interact(self):
+ """
+ Called once per widget per interaction.
+ """
+
+ return None
+
+ def predict_one(self):
+ """
+ Called to ask this displayable to call the callback with all
+ the images it may want to load.
+ """
+
+ return
+
+ def predict_one_action(self):
+ """
+ Called to ask this displayable to cause image prediction
+ to occur for images that may be loaded by its actions.
+ """
+
+ return
+
+ def place(self, dest, x, y, width, height, surf, main=True):
+ """
+ This draws this Displayable onto a destination surface, using
+ the placement style information returned by this object's
+ get_placement() method.
+
+ @param dest: The surface that this displayable will be drawn
+ on.
+
+ @param x: The minimum x coordinate on this surface that this
+ Displayable will be drawn to.
+
+ @param y: The minimum y coordinate on this surface that this
+ displayable will be drawn to.
+
+ @param width: The width of the area allocated to this
+ Displayable.
+
+ @param height: The height of the area allocated to this
+ Displayable.
+
+ @param surf: The surface returned by a previous call to
+ self.render().
+ """
+
+ xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.get_placement()
+
+ if xpos is None:
+ xpos = 0
+ if ypos is None:
+ ypos = 0
+ if xanchor is None:
+ xanchor = 0
+ if yanchor is None:
+ yanchor = 0
+ if xoffset is None:
+ xoffset = 0
+ if yoffset is None:
+ yoffset = 0
+
+ # We need to use type, since isinstance(absolute(0), float).
+ if xpos.__class__ is float:
+ xpos *= width
+
+ if xanchor.__class__ is float:
+ xanchor *= surf.width
+
+ xpos += x + xoffset - xanchor
+
+ # y
+
+ if ypos.__class__ is float:
+ ypos *= height
+
+ if yanchor.__class__ is float:
+ yanchor *= surf.height
+
+ ypos += y + yoffset - yanchor
+
+ if dest is not None:
+ if subpixel:
+ dest.subpixel_blit(surf, (xpos, ypos), main, main, None)
+ else:
+ dest.blit(surf, (xpos, ypos), main, main, None)
+
+ return xpos, ypos
+
+ def set_transform_event(self, event):
+ """
+ Sets the transform event of this displayable to event.
+ """
+
+ if event == self.transform_event:
+ return
+
+ self.transform_event = event
+ if self.transform_event_responder:
+ renpy.display.render.redraw(self, 0)
+
+ def _hide(self, st, at, kind):
+ """
+ Returns None if this displayable is ready to be hidden, or
+ a replacement displayable if it doesn't want to be hidden
+ quite yet. Kind is either "hide" or "replaced".
+ """
+
+ return None
+
+ def _show(self):
+ """
+ Called when the displayable is added to a scene list.
+ """
+
+ def _get_parameterized(self):
+ """
+ If this is a ImageReference to a parameterized image, return
+ the get_parameterized() of the parameterized image. Otherwise,
+ return this displayable.
+ """
+
+ return self
+
+ def _change_transform_child(self, child):
+ """
+ If this is a transform, makes a copy of the transform and sets
+ the child of the innermost transform to this. Otherwise,
+ simply returns child.
+ """
+
+ return child
+
+
+
+class SceneListEntry(renpy.object.Object):
+ """
+ Represents a scene list entry. Since this was replacing a tuple,
+ it should be treated as immutable after its initial creation.
+ """
+
+ def __init__(self, tag, zorder, show_time, animation_time, displayable, name):
+ self.tag = tag
+ self.zorder = zorder
+ self.show_time = show_time
+ self.animation_time = animation_time
+ self.displayable = displayable
+ self.name = name
+
+ def __iter__(self):
+ return iter((self.tag, self.zorder, self.show_time, self.animation_time, self.displayable))
+
+ def __getitem__(self, index):
+ return (self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)[index]
+
+ def __repr__(self):
+ return "<SLE: %r %r %r>" % (self.tag, self.name, self.displayable)
+
+ def copy(self):
+ return SceneListEntry(
+ self.tag,
+ self.zorder,
+ self.show_time,
+ self.animation_time,
+ self.displayable,
+ self.name)
+
+ def update_time(self, time):
+
+ rv = self
+
+ if self.show_time is None or self.animation_time is None:
+ rv = self.copy()
+ rv.show_time = rv.show_time or time
+ rv.animation_time = rv.animation_time or time
+
+ return rv
+
+
+class SceneLists(renpy.object.Object):
+ """
+ This stores the current scene lists that are being used to display
+ things to the user.
+ """
+
+ __version__ = 6
+
+ def after_setstate(self):
+ for i in renpy.config.layers + renpy.config.top_layers:
+ if i not in self.layers:
+ self.layers[i] = [ ]
+ self.at_list[i] = { }
+ self.layer_at_list[i] = (None, [ ])
+
+ def after_upgrade(self, version):
+
+ if version < 1:
+
+ self.at_list = { }
+ self.layer_at_list = { }
+
+ for i in renpy.config.layers + renpy.config.top_layers:
+ self.at_list[i] = { }
+ self.layer_at_list[i] = (None, [ ])
+
+ if version < 3:
+ self.shown_window = False
+
+ if version < 4:
+ for k in self.layers:
+ self.layers[k] = [ SceneListEntry(*(i + (None,)) ) for i in self.layers[k] ]
+
+ self.additional_transient = [ ]
+
+ if version < 5:
+ self.drag_group = None
+
+ if version < 6:
+ self.shown = self.image_predict_info
+
+ def __init__(self, oldsl, shown):
+
+ super(SceneLists, self).__init__()
+
+ # Has a window been shown as part of these scene lists?
+ self.shown_window = False
+
+ # A map from layer name -> list(SceneListEntry)
+ self.layers = { }
+
+ # A map from layer name -> tag -> at_list associated with that tag.
+ self.at_list = { }
+
+ # A map from layer to (star time, at_list), where the at list has
+ # been applied to the layer as a whole.
+ self.layer_at_list = { }
+
+ # The current shown images,
+ self.shown = shown
+
+ # A list of (layer, tag) pairs that are considered to be
+ # transient.
+ self.additional_transient = [ ]
+
+ # Either None, or a DragGroup that's used as the default for
+ # drags with names.
+ self.drag_group = None
+
+ if oldsl:
+
+ for i in renpy.config.layers + renpy.config.top_layers:
+
+ try:
+ self.layers[i] = oldsl.layers[i][:]
+ except KeyError:
+ self.layers[i] = [ ]
+
+ if i in oldsl.at_list:
+ self.at_list[i] = oldsl.at_list[i].copy()
+ self.layer_at_list[i] = oldsl.layer_at_list[i]
+ else:
+ self.at_list[i] = { }
+ self.layer_at_list[i] = (None, [ ])
+
+ for i in renpy.config.overlay_layers:
+ self.clear(i)
+
+ self.replace_transient()
+
+ self.focused = None
+
+ self.drag_group = oldsl.drag_group
+
+ else:
+ for i in renpy.config.layers + renpy.config.top_layers:
+ self.layers[i] = [ ]
+ self.at_list[i] = { }
+ self.layer_at_list[i] = (None, [ ])
+
+ self.music = None
+ self.focused = None
+
+ def replace_transient(self):
+ """
+ Replaces the contents of the transient display list with
+ a copy of the master display list. This is used after a
+ scene is displayed to get rid of transitions and interface
+ elements.
+ """
+
+ for i in renpy.config.transient_layers:
+ self.clear(i, True)
+
+ for layer, tag in self.additional_transient:
+ self.remove(layer, tag)
+
+ self.additional_transient = [ ]
+
+ def transient_is_empty(self):
+ """
+ This returns True if all transient layers are empty. This is
+ used by the rollback code, as we can't start a new rollback
+ if there is something in a transient layer (as things in the
+ transient layer may contain objects that cannot be pickled,
+ like lambdas.)
+ """
+
+ for i in renpy.config.transient_layers:
+ if self.layers[i]:
+ return False
+
+ return True
+
+ def transform_state(self, old_thing, new_thing):
+ """
+ If the old thing is a transform, then move the state of that transform
+ to the new thing.
+ """
+
+ if old_thing is None:
+ return new_thing
+
+ # Don't bother wrapping screens, as they can't be transformed.
+ if isinstance(new_thing, renpy.display.screen.ScreenDisplayable):
+ return new_thing
+
+ old_transform = old_thing._get_parameterized()
+ if not isinstance(old_transform, renpy.display.motion.Transform):
+ return new_thing
+
+ new_transform = new_thing._get_parameterized()
+ if not isinstance(new_transform, renpy.display.motion.Transform):
+ new_thing = new_transform = renpy.display.motion.Transform(child=new_thing)
+
+ new_transform.take_state(old_transform)
+ return new_thing
+
+
+ def find_index(self, layer, tag, zorder, behind):
+ """
+ This finds the spot in the named layer where we should insert the
+ displayable. It returns two things: an index at which the new thing
+ should be added, and an index at which the old thing should be hidden.
+ (Note that the indexes are relative to the current state of the list,
+ which may change on an add or remove.)
+ """
+
+ add_index = None
+ remove_index = None
+
+
+ for i, sle in enumerate(self.layers[layer]):
+
+ if add_index is None:
+
+ if sle.zorder == zorder:
+ if sle.tag and (sle.tag == tag or sle.tag in behind):
+ add_index = i
+
+ elif sle.zorder > zorder:
+ add_index = i
+
+
+ if remove_index is None:
+ if (sle.tag and sle.tag == tag) or sle.displayable == tag:
+ remove_index = i
+
+
+ if add_index is None:
+ add_index = len(self.layers[layer])
+
+ return add_index, remove_index
+
+
+ def add(self,
+ layer,
+ thing,
+ key=None,
+ zorder=0,
+ behind=[ ],
+ at_list=[ ],
+ name=None,
+ atl=None,
+ default_transform=None,
+ transient=False):
+ """
+ Adds something to this scene list. Some of these names are quite a bit
+ out of date.
+
+ `thing` - The displayable to add.
+
+ `key` - A string giving the tag associated with this thing.
+
+ `zorder` - Where to place this thing in the zorder, an integer
+ A greater value means closer to the user.
+
+ `behind` - A list of tags to place the thing behind.
+
+ `at_list` - The at_list associated with this
+ displayable. Counterintunitively, this is not actually
+ applied, but merely stored for future use.
+
+ `name` - The full name of the image being displayed. This is used for
+ image lookup.
+
+ `atl` - If not None, an atl block applied to the thing. (This actually is
+ applied here.)
+
+ `default_transform` - The default transform that is used to initialized
+ the values in the other transforms.
+ """
+
+ if not isinstance(thing, Displayable):
+ raise Exception("Attempting to show something that isn't a displayable:" + repr(thing))
+
+ if layer not in self.layers:
+ raise Exception("Trying to add something to non-existent layer '%s'." % layer)
+
+ if key:
+ self.remove_hide_replaced(layer, key)
+ self.at_list[layer][key] = at_list
+
+ if key and name:
+ self.shown.predict_show(layer, name)
+
+ if transient:
+ self.additional_transient.append((layer, key))
+
+ l = self.layers[layer]
+
+ if atl:
+ thing = renpy.display.motion.ATLTransform(atl, child=thing)
+
+ add_index, remove_index = self.find_index(layer, key, zorder, behind)
+
+ at = None
+ st = None
+
+ if remove_index is not None:
+ sle = l[remove_index]
+ at = sle.animation_time
+ old = sle.displayable
+
+ if (not atl and
+ not at_list and
+ renpy.config.keep_running_transform and
+ isinstance(old, renpy.display.motion.Transform)):
+
+ thing = sle.displayable._change_transform_child(thing)
+ else:
+ thing = self.transform_state(l[remove_index].displayable, thing)
+
+ thing.set_transform_event("replace")
+ thing._show()
+
+ else:
+
+ if not isinstance(thing, renpy.display.motion.Transform):
+ thing = self.transform_state(default_transform, thing)
+
+ thing.set_transform_event("show")
+ thing._show()
+
+ sle = SceneListEntry(key, zorder, st, at, thing, name)
+ l.insert(add_index, sle)
+
+ if remove_index is not None:
+ if add_index <= remove_index:
+ remove_index += 1
+
+ self.hide_or_replace(layer, remove_index, "replaced")
+
+ def hide_or_replace(self, layer, index, prefix):
+ """
+ Hides or replaces the scene list entry at the given
+ index. `prefix` is a prefix that is used if the entry
+ decides it doesn't want to be hidden quite yet.
+ """
+
+ if index is None:
+ return
+
+ l = self.layers[layer]
+ oldsle = l[index]
+
+ now = get_time()
+
+ st = oldsle.show_time or now
+ at = oldsle.animation_time or now
+
+ if oldsle.tag:
+
+ d = oldsle.displayable._hide(now - st, now - at, prefix)
+
+ # _hide can mutate the layers, so we need to recompute
+ # index.
+ index = l.index(oldsle)
+
+ if d is not None:
+
+ sle = SceneListEntry(
+ prefix + "$" + oldsle.tag,
+ oldsle.zorder,
+ st,
+ at,
+ d,
+ None)
+
+ l[index] = sle
+
+ return
+
+ l.pop(index)
+
+ def get_all_displayables(self):
+ """
+ Gets all displayables reachable from this scene list.
+ """
+
+ rv = [ ]
+ for l in self.layers.values():
+ for sle in l:
+ rv.append(sle.displayable)
+
+ return rv
+
+ def remove_above(self, layer, thing):
+ """
+ Removes everything on the layer that is closer to the user
+ than thing, which may be either a tag or a displayable. Thing must
+ be displayed, or everything will be removed.
+ """
+
+ for i in reversed(range(len(self.layers[layer]))):
+
+ sle = self.layers[layer][i]
+
+ if thing:
+ if sle.tag == thing or sle.displayable == thing:
+ break
+
+ if sle.tag and "$" in sle.tag:
+ continue
+
+ self.hide_or_replace(layer, i, "hide")
+
+ def remove(self, layer, thing):
+ """
+ Thing is either a key or a displayable. This iterates through the
+ named layer, searching for entries matching the thing.
+ When they are found, they are removed from the displaylist.
+
+ It's not an error to remove something that isn't in the layer in
+ the first place.
+ """
+
+ if layer not in self.layers:
+ raise Exception("Trying to remove something from non-existent layer '%s'." % layer)
+
+ _add_index, remove_index = self.find_index(layer, thing, 0, [ ])
+
+ if remove_index is not None:
+ tag = self.layers[layer][remove_index].tag
+
+ if tag:
+ self.shown.predict_hide(layer, (tag,))
+ self.at_list[layer].pop(tag, None)
+
+ self.hide_or_replace(layer, remove_index, "hide")
+
+ def clear(self, layer, hide=False):
+ """
+ Clears the named layer, making it empty.
+
+ If hide is True, then objects are hidden. Otherwise, they are
+ totally wiped out.
+ """
+
+ if not hide:
+ self.layers[layer] = [ ]
+
+ else:
+
+ # Have to iterate in reverse order, since otherwise
+ # the indexes might change.
+ for i in reversed(range(len(self.layers[layer]))):
+ self.hide_or_replace(layer, i, hide)
+
+ self.at_list[layer].clear()
+ self.shown.predict_scene(layer)
+ self.layer_at_list[layer] = (None, [ ])
+
+ def set_layer_at_list(self, layer, at_list):
+ self.layer_at_list[layer] = (None, list(at_list))
+
+ def set_times(self, time):
+ """
+ This finds entries with a time of None, and replaces that
+ time with the given time.
+ """
+
+ for l, (t, list) in list(self.layer_at_list.items()): #@ReservedAssignment
+ self.layer_at_list[l] = (t or time, list)
+
+ for l, ll in self.layers.items():
+ self.layers[l] = [ i.update_time(time) for i in ll ]
+
+ def showing(self, layer, name):
+ """
+ Returns true if something with the prefix of the given name
+ is found in the scene list.
+ """
+
+ return self.shown.showing(layer, name)
+
+ def make_layer(self, layer, properties):
+ """
+ Creates a Fixed with the given layer name and scene_list.
+ """
+
+ rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **properties)
+ rv.append_scene_list(self.layers[layer])
+
+ time, at_list = self.layer_at_list[layer]
+
+ if at_list:
+ for a in at_list:
+
+ if isinstance(a, renpy.display.motion.Transform):
+ rv = a(child=rv)
+ else:
+ rv = a(rv)
+
+ f = renpy.display.layout.MultiBox(layout='fixed')
+ f.add(rv, time, time)
+ rv = f
+
+ rv.layer_name = layer
+ return rv
+
+ def remove_hide_replaced(self, layer, tag):
+ """
+ Removes things that are hiding or replaced, that have the given
+ tag.
+ """
+
+ hide_tag = "hide$" + tag
+ replaced_tag = "replaced$" + tag
+
+ l = self.layers[layer]
+ self.layers[layer] = [ i for i in l if i.tag != hide_tag and i.tag != replaced_tag ]
+
+ def remove_hidden(self):
+ """
+ Goes through all of the layers, and removes things that are
+ hidden and are no longer being kept alive by their hide
+ methods.
+ """
+
+ now = get_time()
+
+ for l in self.layers:
+ newl = [ ]
+
+ for sle in self.layers[l]:
+
+ if sle.tag:
+
+ if sle.tag.startswith("hide$"):
+ d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "hide")
+ if not d:
+ continue
+
+ elif sle.tag.startswith("replaced$"):
+ d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "replaced")
+ if not d:
+ continue
+
+ newl.append(sle)
+
+ self.layers[l] = newl
+
+ def get_displayable_by_tag(self, layer, tag):
+ """
+ Returns the displayable on the layer with the given tag, or None
+ if no such displayable exists. Note that this will usually return
+ a Transform.
+ """
+
+ if layer not in self.layers:
+ raise Exception("Unknown layer %r." % layer)
+
+ for sle in self.layers[layer]:
+ if sle.tag == tag:
+ return sle.displayable
+
+ return None
+
+ def get_displayable_by_name(self, layer, name):
+ """
+ Returns the displayable on the layer with the given tag, or None
+ if no such displayable exists. Note that this will usually return
+ a Transform.
+ """
+
+ if layer not in self.layers:
+ raise Exception("Unknown layer %r." % layer)
+
+ for sle in self.layers[layer]:
+
+ if sle.name == name:
+ return sle.displayable
+
+ return None
+
+
+def scene_lists(index=-1):
+ """
+ Returns either the current scenelists object, or the one for the
+ context at the given index.
+ """
+
+ return renpy.game.context(index).scene_lists
+
+
+class Interface(object):
+ """
+ This represents the user interface that interacts with the user.
+ It manages the Display objects that display things to the user, and
+ also handles accepting and responding to user input.
+
+ @ivar display: The display that we used to display the screen.
+
+ @ivar profile_time: The time of the last profiling.
+
+ @ivar screenshot: A screenshot, or None if no screenshot has been
+ taken.
+
+ @ivar old_scene: The last thing that was displayed to the screen.
+
+ @ivar transition: A map from layer name to the transition that will
+ be applied the next time interact restarts.
+
+ @ivar transition_time: A map from layer name to the time the transition
+ involving that layer started.
+
+ @ivar transition_from: A map from layer name to the scene that we're
+ transitioning from on that layer.
+
+ @ivar suppress_transition: If True, then the next transition will not
+ happen.
+
+ @ivar force_redraw: If True, a redraw is forced.
+
+ @ivar restart_interaction: If True, the current interaction will
+ be restarted.
+
+ @ivar pushed_event: If not None, an event that was pushed back
+ onto the stack.
+
+ @ivar mouse: The name of the mouse cursor to use during the current
+ interaction.
+
+ @ivar ticks: The number of 20hz ticks.
+
+ @ivar frame_time: The time at which we began drawing this frame.
+
+ @ivar interact_time: The time of the start of the first frame of the current interact_core.
+
+ @ivar time_event: A singleton ignored event.
+
+ @ivar event_time: The time of the current event.
+
+ @ivar timeout_time: The time at which the timeout will occur.
+ """
+
+ def __init__(self):
+ self.screenshot = None
+ self.old_scene = { }
+ self.transition = { }
+ self.ongoing_transition = { }
+ self.transition_time = { }
+ self.transition_from = { }
+ self.suppress_transition = False
+ self.quick_quit = False
+ self.force_redraw = False
+ self.restart_interaction = False
+ self.pushed_event = None
+ self.ticks = 0
+ self.mouse = 'default'
+ self.timeout_time = None
+ self.last_event = None
+ self.current_context = None
+ self.roll_forward = None
+
+ # Things to be preloaded.
+ self.preloads = [ ]
+
+ # The time at which this draw occurs.
+ self.frame_time = 0
+
+ # The time when this interaction occured.
+ self.interact_time = None
+
+ # The time we last tried to quit.
+ self.quit_time = 0
+
+ self.time_event = pygame.event.Event(TIMEEVENT)
+ self.redraw_event = pygame.event.Event(REDRAW)
+
+ # Are we focused?
+ self.focused = True
+
+ # Properties for each layer.
+ self.layer_properties = { }
+
+ # Have we shown the window this interaction?
+ self.shown_window = False
+
+ # Are we in fullscren mode?
+ self.fullscreen = False
+
+ for layer in renpy.config.layers + renpy.config.top_layers:
+ if layer in renpy.config.layer_clipping:
+ x, y, w, h = renpy.config.layer_clipping[layer]
+ self.layer_properties[layer] = dict(
+ xpos = x,
+ xanchor = 0,
+ ypos = y,
+ yanchor = 0,
+ xmaximum = w,
+ ymaximum = h,
+ xminimum = w,
+ yminimum = h,
+ clipping = True,
+ )
+
+ else:
+ self.layer_properties[layer] = dict()
+
+
+ # A stack giving the values of self.transition and self.transition_time
+ # for contexts outside the current one. This is used to restore those
+ # in the case where nothing has changed in the new context.
+ self.transition_info_stack = [ ]
+
+ # The time when the event was dispatched.
+ self.event_time = 0
+
+ # The time we saw the last mouse event.
+ self.mouse_event_time = None
+
+ # Should we show the mouse?
+ self.show_mouse = True
+
+ # Should we reset the display?
+ self.display_reset = False
+
+ # The last size we were resized to. This lets us debounce the
+ # VIDEORESIZE event.
+ self.last_resize = None
+
+ # Ensure that we kill off the presplash.
+ renpy.display.presplash.end()
+
+ # Initialize pygame.
+ if pygame.version.vernum < (1, 8, 1):
+ raise Exception("Ren'Py requires pygame 1.8.1 to run.")
+
+ try:
+ import pygame.macosx as macosx
+ macosx.init() #@UndefinedVariable
+ except:
+ pass
+
+ try:
+ macosx.Video_AutoInit() #@UndefinedVariable
+ except:
+ pass
+
+ pygame.font.init()
+ renpy.audio.audio.init()
+ renpy.display.joystick.init()
+ pygame.display.init()
+
+ # Init timing.
+ init_time()
+ self.profile_time = get_time()
+ self.mouse_event_time = get_time()
+
+ # The current window caption.
+ self.window_caption = None
+
+ renpy.game.interface = self
+ renpy.display.interface = self
+
+ # Are we in safe mode, from holding down shift at start?
+ self.safe_mode = False
+ if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT):
+ self.safe_mode = True
+
+ # Setup the video mode.
+ self.set_mode()
+
+ # Load the image fonts.
+ renpy.text.font.load_image_fonts()
+
+ # Setup the android keymap.
+ if android is not None:
+ android.map_key(android.KEYCODE_BACK, pygame.K_PAGEUP)
+ android.map_key(android.KEYCODE_MENU, pygame.K_ESCAPE)
+
+ # Double check, since at least on Linux, we can't set safe_mode until
+ # the window maps.
+ if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT):
+ self.safe_mode = True
+
+ # Setup periodic event.
+ pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
+
+ # Don't grab the screen.
+ pygame.event.set_grab(False)
+
+ # Do we need a background screenshot?
+ self.bgscreenshot_needed = False
+
+ # Event used to signal background screenshot taken.
+ self.bgscreenshot_event = threading.Event()
+
+ # The background screenshot surface.
+ self.bgscreenshot_surface = None
+
+
+ def post_init(self):
+ # Setup.
+
+ # Needed for Unity.
+ wmclass = renpy.config.save_directory or os.path.basename(sys.argv[0])
+ os.environ['SDL_VIDEO_X11_WMCLASS'] = wmclass
+
+ self.set_window_caption(force=True)
+ self.set_icon()
+
+ if renpy.config.key_repeat is not None:
+ delay, repeat_delay = renpy.config.key_repeat
+ pygame.key.set_repeat(int(1000 * delay), int(1000 * repeat_delay))
+
+ def set_icon(self):
+ """
+ This is called to set up the window icon.
+ """
+
+ # Window icon.
+ icon = renpy.config.window_icon
+
+ if renpy.windows and renpy.config.windows_icon:
+ icon = renpy.config.windows_icon
+
+ if icon:
+
+ im = renpy.display.scale.image_load_unscaled(
+ renpy.loader.load(icon),
+ icon,
+ convert=False,
+ )
+
+ # Convert the aspect ratio to be square.
+ iw, ih = im.get_size()
+ imax = max(iw, ih)
+ square_im = renpy.display.pgrender.surface_unscaled((imax, imax), True)
+ square_im.blit(im, ( (imax-iw)/2, (imax-ih)/2 ))
+ im = square_im
+
+ if renpy.windows and im.get_size() != (32, 32):
+ im = renpy.display.scale.real_smoothscale(im, (32, 32))
+
+ pygame.display.set_icon(im)
+
+
+ def set_window_caption(self, force=False):
+ caption = renpy.config.window_title + renpy.store._window_subtitle
+ if not force and caption == self.window_caption:
+ return
+
+ self.window_caption = caption
+ pygame.display.set_caption(caption.encode("utf-8"))
+
+ def iconify(self):
+ pygame.display.iconify()
+
+ def get_draw_constructors(self):
+ """
+ Figures out the list of draw constructors to try.
+ """
+
+ renderer = renpy.game.preferences.renderer
+ renderer = os.environ.get("RENPY_RENDERER", renderer)
+
+ if self.safe_mode:
+ renderer = "sw"
+
+ renpy.config.renderer = renderer
+
+ if renderer == "auto":
+ if renpy.windows:
+ renderers = [ "gl", "angle", "sw" ]
+ else:
+ renderers = [ "gl", "sw" ]
+ else:
+ renderers = [ renderer, "sw" ]
+
+ draw_objects = { }
+
+ def make_draw(name, mod, cls, *args):
+ if name not in renderers:
+ return False
+
+ try:
+ __import__(mod)
+ module = sys.modules[mod]
+ draw_class = getattr(module, cls)
+ draw_objects[name] = draw_class(*args)
+ return True
+
+ except:
+ renpy.display.log.write("Couldn't import {0} renderer:".format(name))
+ renpy.display.log.exception()
+
+ return False
+
+ if renpy.windows:
+ has_angle = make_draw("angle", "renpy.angle.gldraw", "GLDraw")
+ else:
+ has_angle = False
+
+ make_draw("gl", "renpy.gl.gldraw", "GLDraw", not has_angle)
+ make_draw("sw", "renpy.display.swdraw", "SWDraw")
+
+ rv = [ ]
+
+ def append_draw(name):
+ if name in draw_objects:
+ rv.append(draw_objects[name])
+ else:
+ renpy.display.log.write("Unknown renderer: {0}".format(name))
+
+ for i in renderers:
+ append_draw(i)
+
+ return rv
+
+
+ def kill_textures(self):
+ renpy.display.render.free_memory()
+ renpy.text.text.layout_cache_clear()
+
+ def kill_textures_and_surfaces(self):
+ """
+ Kill all textures and surfaces that are loaded.
+ """
+
+ self.kill_textures()
+
+ renpy.display.im.cache.clear()
+ renpy.display.module.bo_cache = None
+
+ def set_mode(self, physical_size=None):
+ """
+ This sets the video mode. It also picks the draw object.
+ """
+
+ # Ensure that we kill off the movie when changing screen res.
+ if renpy.display.draw and renpy.display.draw.info["renderer"] == "sw":
+ renpy.display.video.movie_stop(clear=False)
+
+ if self.display_reset:
+ renpy.display.draw.deinit()
+
+ if renpy.display.draw.info["renderer"] == "angle":
+ renpy.display.draw.quit()
+
+ renpy.display.render.free_memory()
+ renpy.display.im.cache.clear()
+ renpy.text.text.layout_cache_clear()
+
+ renpy.display.module.bo_cache = None
+
+ self.kill_textures_and_surfaces()
+
+ self.display_reset = False
+
+ virtual_size = (renpy.config.screen_width, renpy.config.screen_height)
+
+ if physical_size is None:
+ if renpy.android or renpy.game.preferences.physical_size is None: #@UndefinedVariable
+ physical_size = (renpy.config.screen_width, renpy.config.screen_height)
+ else:
+ physical_size = renpy.game.preferences.physical_size
+
+ # Setup screen.
+ fullscreen = renpy.game.preferences.fullscreen
+
+ # If we're in fullscreen mode, and changing to another mode, go to
+ # windowed mode first.
+ s = pygame.display.get_surface()
+ if s and (s.get_flags() & pygame.FULLSCREEN):
+ fullscreen = False
+
+ old_fullscreen = self.fullscreen
+ self.fullscreen = fullscreen
+
+ if os.environ.get('RENPY_DISABLE_FULLSCREEN', False):
+ fullscreen = False
+ self.fullscreen = renpy.game.preferences.fullscreen
+
+ if renpy.display.draw:
+ draws = [ renpy.display.draw ]
+ else:
+ draws = self.get_draw_constructors()
+
+ for draw in draws:
+ if draw.set_mode(virtual_size, physical_size, fullscreen):
+ renpy.display.draw = draw
+ break
+ else:
+ pygame.display.quit()
+ else:
+ # Ensure we don't get stuck in fullscreen.
+ renpy.game.preferences.fullscreen = False
+ raise Exception("Could not set video mode.")
+
+ # Save the video size.
+ if renpy.config.save_physical_size and not fullscreen and not old_fullscreen:
+ renpy.game.preferences.physical_size = renpy.display.draw.get_physical_size()
+
+ if android:
+ android.init()
+
+ # We need to redraw the (now blank) screen.
+ self.force_redraw = True
+
+ # Assume we have focus until told otherwise.
+ self.focused = True
+
+ # Assume we're not minimized.
+ self.minimized = False
+
+ # Force an interaction restart.
+ self.restart_interaction = True
+
+
+ def draw_screen(self, root_widget, fullscreen_video, draw):
+
+ surftree = renpy.display.render.render_screen(
+ root_widget,
+ renpy.config.screen_width,
+ renpy.config.screen_height,
+ )
+
+ if draw:
+ renpy.display.draw.draw_screen(surftree, fullscreen_video)
+
+ renpy.display.render.mark_sweep()
+ renpy.display.focus.take_focuses()
+
+ self.surftree = surftree
+ self.fullscreen_video = fullscreen_video
+
+
+ def take_screenshot(self, scale, background=False):
+ """
+ This takes a screenshot of the current screen, and stores it so
+ that it can gotten using get_screenshot()
+
+ `background`
+ If true, we're in a background thread. So queue the request
+ until it can be handled by the main thread.
+ """
+
+ if background:
+ self.bgscreenshot_event.clear()
+ self.bgscreenshot_needed = True
+ self.bgscreenshot_event.wait()
+
+ window = self.bgscreenshot_surface
+ self.bgscreenshot_surface = None
+
+ else:
+
+ window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+
+ surf = renpy.display.pgrender.copy_surface(window, True)
+ surf = renpy.display.scale.smoothscale(surf, scale)
+ surf = surf.convert()
+
+ sio = io.StringIO()
+ renpy.display.module.save_png(surf, sio, 0)
+ self.screenshot = sio.getvalue()
+ sio.close()
+
+
+ def save_screenshot(self, filename):
+ """
+ Saves a full-size screenshot in the given filename.
+ """
+
+ window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+
+ if renpy.config.screenshot_crop:
+ window = window.subsurface(renpy.config.screenshot_crop)
+
+ try:
+ renpy.display.scale.image_save_unscaled(window, filename)
+ except:
+ if renpy.config.debug:
+ raise
+ pass
+
+
+ def get_screenshot(self):
+ """
+ Gets the current screenshot, as a string. Returns None if there isn't
+ a current screenshot.
+ """
+
+ rv = self.screenshot
+
+ if not rv:
+ self.take_screenshot((renpy.config.thumbnail_width, renpy.config.thumbnail_height))
+ rv = self.screenshot
+ self.lose_screenshot()
+
+ return rv
+
+
+ def lose_screenshot(self):
+ """
+ This deallocates the saved screenshot.
+ """
+
+ self.screenshot = None
+
+
+ def show_window(self):
+
+ if not renpy.store._window:
+ return
+
+ if renpy.game.context().scene_lists.shown_window:
+ return
+
+ if renpy.config.empty_window:
+ renpy.config.empty_window()
+
+ def do_with(self, trans, paired, clear=False):
+
+ if renpy.config.with_callback:
+ trans = renpy.config.with_callback(trans, paired)
+
+ if (not trans) or self.suppress_transition:
+ self.with_none()
+ return False
+ else:
+ self.set_transition(trans)
+ return self.interact(trans_pause=True,
+ suppress_overlay=not renpy.config.overlay_during_with,
+ mouse='with',
+ clear=clear)
+
+ def with_none(self):
+ """
+ Implements the with None command, which sets the scene we will
+ be transitioning from.
+ """
+
+ renpy.exports.say_attributes = None
+
+ # Show the window, if that's necessary.
+ self.show_window()
+
+ # Compute the overlay.
+ self.compute_overlay()
+
+ scene_lists = renpy.game.context().scene_lists
+
+ # Compute the scene.
+ self.old_scene = self.compute_scene(scene_lists)
+
+ # Get rid of transient things.
+
+ for i in renpy.config.overlay_layers:
+ scene_lists.clear(i)
+
+ scene_lists.replace_transient()
+ scene_lists.shown_window = False
+
+
+ def set_transition(self, transition, layer=None, force=False):
+ """
+ Sets the transition that will be performed as part of the next
+ interaction.
+ """
+
+ if self.suppress_transition and not force:
+ return
+
+ if transition is None:
+ self.transition.pop(layer, None)
+ else:
+ self.transition[layer] = transition
+
+
+ def event_peek(self):
+ """
+ This peeks the next event. It returns None if no event exists.
+ """
+
+ if self.pushed_event:
+ return self.pushed_event
+
+ ev = pygame.event.poll()
+
+ if ev.type == pygame.NOEVENT:
+ # Seems to prevent the CPU from speeding up.
+ renpy.display.draw.event_peek_sleep()
+ return None
+
+ self.pushed_event = ev
+
+ return ev
+
+ def event_poll(self):
+ """
+ Called to busy-wait for an event while we're waiting to
+ redraw a frame.
+ """
+
+ if self.pushed_event:
+ rv = self.pushed_event
+ self.pushed_event = None
+ else:
+ rv = pygame.event.poll()
+
+ self.last_event = rv
+
+ return rv
+
+
+ def event_wait(self):
+ """
+ This is in its own function so that we can track in the
+ profiler how much time is spent in interact.
+ """
+
+ if self.pushed_event:
+ rv = self.pushed_event
+ self.pushed_event = None
+ self.last_event = rv
+ return rv
+
+ # Handle a request for a background screenshot.
+ if self.bgscreenshot_needed:
+ self.bgscreenshot_needed = False
+ self.bgscreenshot_surface = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+ self.bgscreenshot_event.set()
+
+ try:
+ cpu_idle.set()
+ ev = pygame.event.wait()
+ finally:
+ cpu_idle.clear()
+
+ self.last_event = ev
+
+ return ev
+
+
+ def compute_overlay(self):
+
+ if renpy.store.suppress_overlay:
+ return
+
+ # Figure out what the overlay layer should look like.
+ renpy.ui.layer("overlay")
+
+ for i in renpy.config.overlay_functions:
+ i()
+
+ if renpy.game.context().scene_lists.shown_window:
+ for i in renpy.config.window_overlay_functions:
+ i()
+
+ renpy.ui.close()
+
+
+ def compute_scene(self, scene_lists):
+ """
+ This converts scene lists into a dictionary mapping layer
+ name to a Fixed containing that layer.
+ """
+
+ rv = { }
+
+ for layer in renpy.config.layers + renpy.config.top_layers:
+ rv[layer] = scene_lists.make_layer(layer, self.layer_properties[layer])
+
+ root = renpy.display.layout.MultiBox(layout='fixed')
+ root.layers = { }
+
+ for layer in renpy.config.layers:
+ root.layers[layer] = rv[layer]
+ root.add(rv[layer])
+ rv[None] = root
+
+ return rv
+
+
+ def quit_event(self):
+ """
+ This is called to handle the user invoking a quit.
+ """
+
+ if self.quit_time > (time.time() - .75):
+ raise renpy.game.QuitException()
+
+ if renpy.config.quit_action is not None:
+ self.quit_time = time.time()
+
+ # Make the screen more suitable for interactions.
+ renpy.exports.movie_stop(only_fullscreen=True)
+ renpy.store.mouse_visible = True
+
+ renpy.display.behavior.run(renpy.config.quit_action)
+ else:
+ raise renpy.game.QuitException()
+
+
+ def get_mouse_info(self):
+ # Figure out if the mouse visibility algorithm is hiding the mouse.
+ if self.mouse_event_time + renpy.config.mouse_hide_time < renpy.display.core.get_time():
+ visible = False
+ else:
+ visible = renpy.store.mouse_visible and (not renpy.game.less_mouse)
+
+ visible = visible and self.show_mouse
+
+ # If not visible, hide the mouse.
+ if not visible:
+ return False, 0, 0, None
+
+ # Deal with a hardware mouse, the easy way.
+ if not renpy.config.mouse:
+ return True, 0, 0, None
+
+ # Deal with the mouse going offscreen.
+ if not self.focused:
+ return False, 0, 0, None
+
+ mouse_kind = renpy.display.focus.get_mouse() or self.mouse
+
+ # Figure out the mouse animation.
+ if mouse_kind in renpy.config.mouse:
+ anim = renpy.config.mouse[mouse_kind]
+ else:
+ anim = renpy.config.mouse[getattr(renpy.store, 'default_mouse', 'default')]
+
+ img, x, y = anim[self.ticks % len(anim)]
+ tex = renpy.display.im.load_image(img)
+
+ return False, x, y, tex
+
+ def drawn_since(self, seconds_ago):
+ """
+ Returns true if the screen has been drawn in the last `seconds_ago`,
+ and false otherwise.
+ """
+
+ return (get_time() - self.frame_time) <= seconds_ago
+
+ def android_check_suspend(self):
+
+ if android.check_pause():
+
+ android.sound.pause_all()
+
+ pygame.time.set_timer(PERIODIC, 0)
+ pygame.time.set_timer(REDRAW, 0)
+ pygame.time.set_timer(TIMEEVENT, 0)
+
+ # The game has to be saved.
+ renpy.loadsave.save("_reload-1")
+
+ android.wait_for_resume()
+
+ # Since we came back to life, we can get rid of the
+ # auto-reload.
+ renpy.loadsave.unlink_save("_reload-1")
+
+ pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
+
+ android.sound.unpause_all()
+
+ def iconified(self):
+ """
+ Called when we become an icon.
+ """
+
+ if self.minimized:
+ return
+
+ self.minimized = True
+
+ renpy.display.log.write("The window was minimized.")
+
+
+ def restored(self):
+ """
+ Called when we are restored from being an icon.
+ """
+
+ # This is necessary on Windows/DirectX/Angle, as otherwise we get
+ # a blank screen.
+
+ if not self.minimized:
+ return
+
+ self.minimized = False
+
+ renpy.display.log.write("The window was restored.")
+
+ if renpy.windows:
+ self.display_reset = True
+ self.set_mode(self.last_resize)
+
+ def enter_context(self):
+ """
+ Called when we enter a new context.
+ """
+
+ # Stop ongoing transitions.
+ self.ongoing_transition.clear()
+ self.transition_from.clear()
+ self.transition_time.clear()
+
+ def post_time_event(self):
+ """
+ Posts a time_event object to the queue.
+ """
+
+ try:
+ pygame.event.post(self.time_event)
+ except:
+ pass
+
+ def interact(self, clear=True, suppress_window=False, **kwargs):
+ """
+ This handles an interaction, restarting it if necessary. All of the
+ keyword arguments are passed off to interact_core.
+ """
+
+ # Cancel magic error reporting.
+ renpy.bootstrap.report_error = None
+
+ context = renpy.game.context()
+
+ if context.interacting:
+ raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.")
+
+ context.interacting = True
+
+
+ # Show a missing window.
+ if not suppress_window:
+ self.show_window()
+
+ # These things can be done once per interaction.
+
+ preloads = self.preloads
+ self.preloads = [ ]
+
+ try:
+ renpy.game.after_rollback = False
+
+ for i in renpy.config.start_interact_callbacks:
+ i()
+
+ repeat = True
+
+ while repeat:
+ repeat, rv = self.interact_core(preloads=preloads, **kwargs)
+
+ return rv
+
+ finally:
+
+ context.interacting = False
+
+ # Clean out transient stuff at the end of an interaction.
+ if clear:
+ scene_lists = renpy.game.context().scene_lists
+ scene_lists.replace_transient()
+
+ self.ongoing_transition = { }
+ self.transition_time = { }
+ self.transition_from = { }
+
+ self.restart_interaction = True
+
+ renpy.game.context().scene_lists.shown_window = False
+
+ def interact_core(self,
+ show_mouse=True,
+ trans_pause=False,
+ suppress_overlay=False,
+ suppress_underlay=False,
+ mouse='default',
+ preloads=[],
+ roll_forward=None,
+ ):
+
+ """
+ This handles one cycle of displaying an image to the user,
+ and then responding to user input.
+
+ @param show_mouse: Should the mouse be shown during this
+ interaction? Only advisory, and usually doesn't work.
+
+ @param trans_pause: If given, we must have a transition. Should we
+ add a pause behavior during the transition?
+
+ @param suppress_overlay: This suppresses the display of the overlay.
+ @param suppress_underlay: This suppresses the display of the underlay.
+ """
+
+ self.roll_forward = roll_forward
+ self.show_mouse = show_mouse
+
+ suppress_transition = renpy.config.skipping or renpy.game.less_updates
+
+ # The global one.
+ self.suppress_transition = False
+
+ # Figure out transitions.
+ for k in self.transition:
+ if k not in self.old_scene:
+ continue
+
+ self.ongoing_transition[k] = self.transition[k]
+ self.transition_from[k] = self.old_scene[k]
+ self.transition_time[k] = None
+
+ self.transition.clear()
+
+ if suppress_transition:
+ self.ongoing_transition.clear()
+ self.transition_from.clear()
+ self.transition_time.clear()
+
+ ## Safety condition, prevents deadlocks.
+ if trans_pause:
+ if not self.ongoing_transition:
+ return False, None
+ if None not in self.ongoing_transition:
+ return False, None
+ if suppress_transition:
+ return False, None
+ if not self.old_scene:
+ return False, None
+
+ # Check to see if the language has changed.
+ renpy.translation.check_language()
+
+ # We just restarted.
+ self.restart_interaction = False
+
+ # Setup the mouse.
+ self.mouse = mouse
+
+ # The start and end times of this interaction.
+ start_time = get_time()
+ end_time = start_time
+
+ # frames = 0
+
+ for i in renpy.config.interact_callbacks:
+ i()
+
+ # Set the window caption.
+ self.set_window_caption()
+
+ # Tick time forward.
+ renpy.display.im.cache.tick()
+ renpy.text.text.layout_cache_tick()
+ renpy.display.predict.reset()
+
+ # Cleare the size groups.
+ renpy.display.layout.size_groups.clear()
+
+ # Clear some events.
+ pygame.event.clear((pygame.MOUSEMOTION,
+ PERIODIC,
+ TIMEEVENT,
+ REDRAW))
+
+ # Add a single TIMEEVENT to the queue.
+ self.post_time_event()
+
+ # Figure out the scene list we want to show.
+ scene_lists = renpy.game.context().scene_lists
+
+ # Remove the now-hidden things.
+ scene_lists.remove_hidden()
+
+ # Compute the overlay.
+ if not suppress_overlay:
+ self.compute_overlay()
+
+ # The root widget of everything that is displayed on the screen.
+ root_widget = renpy.display.layout.MultiBox(layout='fixed')
+ root_widget.layers = { }
+
+ # A list of widgets that are roots of trees of widgets that are
+ # considered for focusing.
+ focus_roots = [ ]
+
+ # Add the underlay to the root widget.
+ if not suppress_underlay:
+ for i in renpy.config.underlay:
+ root_widget.add(i)
+ focus_roots.append(i)
+
+ if roll_forward is not None:
+ rfw = renpy.display.behavior.RollForward(roll_forward)
+ root_widget.add(rfw)
+ focus_roots.append(rfw)
+
+ # Figure out the scene. (All of the layers, and the root.)
+ scene = self.compute_scene(scene_lists)
+
+ # If necessary, load all images here.
+ for w in scene.values():
+ try:
+ renpy.display.predict.displayable(w)
+ except:
+ pass
+
+ # The root widget of all of the layers.
+ layers_root = renpy.display.layout.MultiBox(layout='fixed')
+ layers_root.layers = { }
+
+ def add_layer(where, layer):
+
+ scene_layer = scene[layer]
+ focus_roots.append(scene_layer)
+
+ if (self.ongoing_transition.get(layer, None) and
+ not suppress_transition):
+
+ trans = self.ongoing_transition[layer](
+ old_widget=self.transition_from[layer],
+ new_widget=scene_layer)
+
+ if not isinstance(trans, Displayable):
+ raise Exception("Expected transition to be a displayable, not a %r" % trans)
+
+ transition_time = self.transition_time.get(layer, None)
+
+ where.add(trans, transition_time, transition_time)
+ where.layers[layer] = trans
+
+ else:
+ where.layers[layer] = scene_layer
+ where.add(scene_layer)
+
+ # Add layers (perhaps with transitions) to the layers root.
+ for layer in renpy.config.layers:
+ add_layer(layers_root, layer)
+
+ # Add layers_root to root_widget, perhaps through a transition.
+ if (self.ongoing_transition.get(None, None) and
+ not suppress_transition):
+
+ old_root = renpy.display.layout.MultiBox(layout='fixed')
+ old_root.layers = { }
+
+ for layer in renpy.config.layers:
+ d = self.transition_from[None].layers[layer]
+ old_root.layers[layer] = d
+ old_root.add(d)
+
+ trans = self.ongoing_transition[None](
+ old_widget=old_root,
+ new_widget=layers_root)
+
+ if not isinstance(trans, Displayable):
+ raise Exception("Expected transition to be a displayable, not a %r" % trans)
+
+ trans._show()
+
+ transition_time = self.transition_time.get(None, None)
+ root_widget.add(trans, transition_time, transition_time)
+
+ if trans_pause:
+ sb = renpy.display.behavior.SayBehavior()
+ root_widget.add(sb)
+ focus_roots.append(sb)
+
+ pb = renpy.display.behavior.PauseBehavior(trans.delay)
+ root_widget.add(pb, transition_time, transition_time)
+ focus_roots.append(pb)
+
+ else:
+ root_widget.add(layers_root)
+
+ # Add top_layers to the root_widget.
+ for layer in renpy.config.top_layers:
+ add_layer(root_widget, layer)
+
+ prediction_coroutine = renpy.display.predict.prediction_coroutine(root_widget)
+ prediction_coroutine.send(None)
+
+ # Clean out the registered adjustments.
+ renpy.display.behavior.adj_registered.clear()
+
+ # Clean up some movie-related things.
+ renpy.display.video.early_interact()
+
+ # Call per-interaction code for all widgets.
+ root_widget.visit_all(lambda i : i.per_interact())
+
+ # Now, update various things regarding scenes and transitions,
+ # so we are ready for a new interaction or a restart.
+ self.old_scene = scene
+
+ # Okay, from here on we now have a single root widget (root_widget),
+ # which we will try to show to the user.
+
+ # Figure out what should be focused.
+ renpy.display.focus.before_interact(focus_roots)
+
+ # Redraw the screen.
+ renpy.display.render.process_redraws()
+ needs_redraw = True
+
+ # First pass through the while loop?
+ first_pass = True
+
+ # We don't yet know when the interaction began.
+ self.interact_time = None
+
+ # We only want to do autosave once.
+ did_autosave = False
+
+ old_timeout_time = None
+ old_redraw_time = None
+
+ rv = None
+
+ # Start sound.
+ renpy.audio.audio.interact()
+
+ # How long until we redraw.
+ redraw_in = 3600
+
+ # Have we drawn a frame yet?
+ video_frame_drawn = False
+
+ # This try block is used to force cleanup even on termination
+ # caused by an exception propagating through this function.
+ try:
+
+ while rv is None:
+
+ # Check for a change in fullscreen preference.
+ if self.fullscreen != renpy.game.preferences.fullscreen or self.display_reset:
+ self.set_mode()
+ needs_redraw = True
+
+ # Check for suspend.
+ if android:
+ self.android_check_suspend()
+
+ # Redraw the screen.
+ if (self.force_redraw or
+ ((first_pass or not pygame.event.peek(ALL_EVENTS)) and
+ renpy.display.draw.should_redraw(needs_redraw, first_pass))):
+
+ self.force_redraw = False
+
+ # If we have a movie, start showing it.
+ fullscreen_video = renpy.display.video.interact()
+
+ # Clean out the redraws, if we have to.
+ # renpy.display.render.kill_redraws()
+
+ # Draw the screen.
+ self.frame_time = get_time()
+
+ if not self.interact_time:
+ self.interact_time = self.frame_time
+
+ self.draw_screen(root_widget, fullscreen_video, (not fullscreen_video) or video_frame_drawn)
+
+ if first_pass:
+ scene_lists.set_times(self.interact_time)
+ for k, v in self.transition_time.items():
+ if v is None:
+ self.transition_time[k] = self.interact_time
+
+ renpy.config.frames += 1
+
+ # If profiling is enabled, report the profile time.
+ if renpy.config.profile :
+ new_time = get_time()
+
+ if new_time - self.profile_time > .015:
+ print("Profile: Redraw took %f seconds." % (new_time - self.frame_time))
+ print("Profile: %f seconds to complete event." % (new_time - self.profile_time))
+
+ if first_pass and self.last_event:
+ x, y = renpy.display.draw.get_mouse_pos()
+ renpy.display.focus.mouse_handler(self.last_event, x, y, default=False)
+
+ needs_redraw = False
+ first_pass = False
+
+ pygame.time.set_timer(REDRAW, 0)
+ pygame.event.clear([REDRAW])
+ old_redraw_time = None
+
+ # Draw the mouse, if it needs drawing.
+ renpy.display.draw.update_mouse()
+
+ # See if we want to restart the interaction entirely.
+ if self.restart_interaction:
+ return True, None
+
+ # Determine if we need a redraw. (We want to run these
+ # functions, so we put them first to prevent short-circuiting.)
+
+ if renpy.display.video.frequent():
+ needs_redraw = True
+ video_frame_drawn = True
+
+ needs_redraw = renpy.display.video.frequent() or needs_redraw
+ needs_redraw = renpy.display.render.process_redraws() or needs_redraw
+
+ # How many seconds until we timeout.
+ timeout_in = 3600
+
+ # Handle the redraw timer.
+ redraw_time = renpy.display.render.redraw_time()
+
+ if (redraw_time is not None) and not needs_redraw:
+ if redraw_time != old_redraw_time:
+ time_left = redraw_time - get_time()
+ time_left = min(time_left, 3600)
+ redraw_in = time_left
+
+ if time_left <= 0:
+ try:
+ pygame.event.post(self.redraw_event)
+ except:
+ pass
+ pygame.time.set_timer(REDRAW, 0)
+ else:
+ pygame.time.set_timer(REDRAW, max(int(time_left * 1000), 1))
+
+ old_redraw_time = redraw_time
+ else:
+ redraw_in = 3600
+ pygame.time.set_timer(REDRAW, 0)
+
+ # Handle the timeout timer.
+ if not self.timeout_time:
+ pygame.time.set_timer(TIMEEVENT, 0)
+ else:
+ time_left = self.timeout_time - get_time()
+ time_left = min(time_left, 3600)
+ timeout_in = time_left
+
+ if time_left <= 0:
+ self.timeout_time = None
+ pygame.time.set_timer(TIMEEVENT, 0)
+ self.post_time_event()
+ elif self.timeout_time != old_timeout_time:
+ # Always set to at least 1ms.
+ pygame.time.set_timer(TIMEEVENT, int(time_left * 1000 + 1))
+ old_timeout_time = self.timeout_time
+
+ # Predict images, if we haven't done so already.
+ while prediction_coroutine is not None:
+
+ # Can we do expensive prediction?
+ expensive_predict = not (needs_redraw or self.event_peek() or renpy.audio.music.is_playing("movie"))
+
+ result = prediction_coroutine.send(expensive_predict)
+
+ if not result:
+ prediction_coroutine = None
+ break
+
+ if not expensive_predict:
+ break
+
+ # If we need to redraw again, do it if we don't have an
+ # event going on.
+ if needs_redraw and not self.event_peek():
+ if renpy.config.profile:
+ self.profile_time = get_time()
+ continue
+
+ # Handle autosaving, as necessary.
+ if not did_autosave and not needs_redraw and not self.event_peek() and redraw_in > .25 and timeout_in > .25:
+ renpy.loadsave.autosave()
+ did_autosave = True
+
+ if needs_redraw or renpy.display.video.playing():
+ ev = self.event_poll()
+ else:
+ ev = self.event_wait()
+
+ if ev.type == pygame.NOEVENT:
+ continue
+
+ if renpy.config.profile:
+ self.profile_time = get_time()
+
+ # Try to merge an TIMEEVENT with other timeevents.
+ if ev.type == TIMEEVENT:
+ old_timeout_time = None
+ pygame.event.clear([TIMEEVENT])
+
+ # On Android, where we have multiple mouse buttons, we can
+ # merge a mouse down and mouse up event with its successor. This
+ # prevents us from getting overwhelmed with too many events on
+ # a multitouch screen.
+ if android and (ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP):
+ pygame.event.clear(ev.type)
+
+ # Handle redraw timeouts.
+ if ev.type == REDRAW:
+ pygame.event.clear([REDRAW])
+ old_redraw_time = None
+ continue
+
+ # Handle periodic events. This includes updating the mouse timers (and through the loop,
+ # the mouse itself), and the audio system periodic calls.
+ if ev.type == PERIODIC:
+ events = 1 + len(pygame.event.get([PERIODIC]))
+ self.ticks += events
+
+ if renpy.config.periodic_callback:
+ renpy.config.periodic_callback()
+
+ renpy.audio.audio.periodic()
+ continue
+
+ # This can set the event to None, to ignore it.
+ ev = renpy.display.joystick.event(ev)
+ if not ev:
+ continue
+
+ # Handle skipping.
+ renpy.display.behavior.skipping(ev)
+
+ # Handle quit specially for now.
+ if ev.type == pygame.QUIT:
+ self.quit_event()
+ continue
+
+ # Handle videoresize.
+ if ev.type == pygame.VIDEORESIZE:
+ evs = pygame.event.get([pygame.VIDEORESIZE])
+ if len(evs):
+ ev = evs[-1]
+
+ if self.last_resize != ev.size:
+ self.last_resize = ev.size
+ self.set_mode((ev.w, ev.h))
+
+ continue
+
+ if ev.type == pygame.MOUSEMOTION or \
+ ev.type == pygame.MOUSEBUTTONDOWN or \
+ ev.type == pygame.MOUSEBUTTONUP:
+
+ self.mouse_event_time = renpy.display.core.get_time()
+
+ # Merge mousemotion events.
+ if ev.type == pygame.MOUSEMOTION:
+ evs = pygame.event.get([pygame.MOUSEMOTION])
+ if len(evs):
+ ev = evs[-1]
+
+ if renpy.windows:
+ self.focused = True
+
+ # Handle focus notifications.
+ if ev.type == pygame.ACTIVEEVENT:
+ if ev.state & 1:
+ self.focused = ev.gain
+
+ if ev.state & 4:
+ if ev.gain:
+ self.restored()
+ else:
+ self.iconified()
+
+ pygame.key.set_mods(0)
+
+ # This returns the event location. It also updates the
+ # mouse state as necessary.
+ x, y = renpy.display.draw.mouse_event(ev)
+
+ if not self.focused:
+ x = -1
+ y = -1
+
+ self.event_time = end_time = get_time()
+
+ try:
+
+ # Handle the event normally.
+ rv = renpy.display.focus.mouse_handler(ev, x, y)
+
+
+ if rv is None:
+ rv = root_widget.event(ev, x, y, 0)
+
+ if rv is None:
+ rv = renpy.display.focus.key_handler(ev)
+
+ if rv is not None:
+ break
+
+ # Handle displayable inspector.
+ if renpy.config.inspector and renpy.display.behavior.inspector(ev):
+ l = self.surftree.main_displayables_at_point(x, y, renpy.config.transient_layers + renpy.config.context_clear_layers + renpy.config.overlay_layers)
+ renpy.game.invoke_in_new_context(renpy.config.inspector, l)
+
+ except IgnoreEvent:
+ # An ignored event can change the timeout. So we want to
+ # process an TIMEEVENT to ensure that the timeout is
+ # set correctly.
+ self.post_time_event()
+
+
+ # Check again after handling the event.
+ needs_redraw |= renpy.display.render.process_redraws()
+
+ if self.restart_interaction:
+ return True, None
+
+ # If we were trans-paused and rv is true, suppress
+ # transitions up to the next interaction.
+ if trans_pause and rv:
+ self.suppress_transition = True
+
+ # But wait, there's more! The finally block runs some cleanup
+ # after this.
+ return False, rv
+
+ except EndInteraction as e:
+ return False, e.value
+
+ finally:
+
+ renpy.exports.say_attributes = None
+
+ # Clean out the overlay layers.
+ for i in renpy.config.overlay_layers:
+ scene_lists.clear(i)
+
+ # Stop ongoing preloading.
+ renpy.display.im.cache.end_tick()
+
+ # We no longer disable periodic between interactions.
+ # pygame.time.set_timer(PERIODIC, 0)
+
+ pygame.time.set_timer(TIMEEVENT, 0)
+ pygame.time.set_timer(REDRAW, 0)
+
+ renpy.game.context().runtime += end_time - start_time
+
+ # Restart the old interaction, which also causes a
+ # redraw if needed.
+ self.restart_interaction = True
+
+ # print "It took", frames, "frames."
+
+ def timeout(self, offset):
+ if offset < 0:
+ return
+
+ if self.timeout_time:
+ self.timeout_time = min(self.event_time + offset, self.timeout_time)
+ else:
+ self.timeout_time = self.event_time + offset
+
diff --git a/unrpyc/renpy/display/dragdrop.py b/unrpyc/renpy/display/dragdrop.py
new file mode 100644
index 0000000..0b99e63
--- /dev/null
+++ b/unrpyc/renpy/display/dragdrop.py
@@ -0,0 +1,731 @@
+# 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.
+
+# TODO: Use overlap (rather than simple pointer location) to determine
+# drag and drop.
+
+import renpy.display
+from renpy.display.render import render, Render, redraw
+from renpy.display.core import absolute
+from renpy.display.behavior import map_event, run
+
+import pygame
+
+def default_drag_group():
+ """
+ Gets the default drag group. If it doesn't exist yet, creates it.
+ """
+
+ sls = renpy.game.context().scene_lists
+
+ rv = sls.drag_group
+
+ if rv is None:
+ rv = DragGroup()
+ sls.drag_group = rv
+
+ return rv
+
+def default_drag_joined(drag):
+ return [ (drag, 0, 0) ]
+
+class Drag(renpy.display.core.Displayable, renpy.python.RevertableObject):
+ """
+ :doc: drag_drop class
+ :args: (d=None, drag_name=None, draggable=True, droppable=True, drag_raise=True, dragged=None, dropped=None, drag_handle=(0.0, 0.0, 1.0, 1.0), drag_joined=..., clicked=None, hovered=None, unhovered=None, **properties)
+
+ A displayable that represents an object that can be dragged around
+ its enclosing area. A Drag can also represent an area that
+ other Drags can be dropped on.
+
+ A Drag can be moved around inside is parent. Generally, its parent
+ should be either a :func:`Fixed` or :class:`DragGroup`.
+
+ A Drag has one child. The child's state reflects the status
+ of the drag and drop operation:
+
+ * ``selected_hover`` - when it is being dragged.
+ * ``selected_idle`` - when it can be dropped on.
+ * ``hover`` - when the draggable will be dragged when the mouse is
+ clicked.
+ * ``idle`` - otherwise.
+
+ The drag handle is a rectangle inside the child. The mouse must be over
+ a non-transparent pixel inside the drag handle for dragging or clicking
+ to occur.
+
+ A newly-created draggable is added to the default DragGroup. A draggable
+ can only be in a single DragGroup - if it's added to a second group,
+ it's removed from the first.
+
+ When a Drag is first rendered, if it's position cannot be determined
+ from the DragGroup it is in, the position of its upper-left corner
+ is computed using the standard layout algorithm. Once that position
+
+
+ `d`
+ If present, the child of this Drag. Drags use the child style
+ in preference to this, if it's not None.
+
+ `drag_name`
+ If not None, the name of this draggable. This is available
+ as the `name` property of draggable objects. If a Drag
+ with the same name is or was in the DragGroup, the starting
+ position of this Drag is taken from that Draggable.
+
+ `draggable`
+ If true, the Drag can be dragged around the screen with
+ the mouse.
+
+ `droppable`
+ If true, other Drags can be dropped on this Drag.
+
+ `drag_raise`
+ If true, this Drag is raised to the top when it is dragged. If
+ it is joined to other Drags, all joined drags are raised.
+
+ `dragged`
+ A callback (or list of callbacks) that is called when the Drag
+ has been dragged. It is called with two arguments. The first is
+ a list of Drags that are being dragged. The second is either
+ a Drag that is being dropped onto, or None of a drop did not
+ occur. If the callback returns a value other than None, that
+ value is returned as the result of the interaction.
+
+ `dropped`
+ A callback (or list of callbacks) that is called when this Drag
+ is dropped onto. It is called with two arguments. The first
+ is the Drag being dropped onto. The second is a list of Drags that
+ are being dragged. If the callback returns a value other than None,
+ that value is returned as the result of the interaction.
+
+ When a dragged and dropped callback are triggered for the same
+ event, the dropped callback is only called if dragged returns
+ None.
+
+ `clicked`
+ A callback this is called, with no arguments, when the Drag is
+ clicked without being moved. A droppable can also be focused
+ and clicked. If the callback returns a value othe than None,
+ that value is returned as the result of the interaction.
+
+ `drag_handle`
+ A (x, y, width, height) tuple, giving the position of the drag
+ handle within the child. In this tuple, integers are considered
+ to be a literal number of pixels, while floats are relative to
+ the size of the child.
+
+ `drag_joined`
+ This is called with the current Drag as an argument. It's
+ expected to return a list of [ (drag, x, y) ] tuples, giving
+ the draggables to drag as a unit. `x` and `y` are the offsets
+ of the drags relative to each other, they are not relative
+ to the corner of this drag.
+
+ Except for `d`, all of the parameters are available as fields (with
+ the same name) on the Drag object. In addition, after the drag has
+ been rendered, the following fields become available:
+
+ `x`, `y`
+ The position of the Drag relative to its parent, in pixels.
+
+ `w`, `h`
+ The width and height of the Drag's child, in pixels.
+ """
+
+ def __init__(self,
+ d=None,
+ drag_name=None,
+ draggable=True,
+ droppable=True,
+ drag_raise=True,
+ dragged=None,
+ dropped=None,
+ drag_handle=(0.0, 0.0, 1.0, 1.0),
+ drag_joined=default_drag_joined,
+ clicked=None,
+ hovered=None,
+ unhovered=None,
+ replaces=None,
+ **properties):
+
+ super(Drag, self).__init__(self, **properties)
+
+ self.drag_name = drag_name
+ self.draggable = draggable
+ self.droppable = droppable
+ self.drag_raise = drag_raise
+ self.dragged = dragged
+ self.dropped = dropped
+ self.drag_handle = drag_handle
+ self.drag_joined = drag_joined
+ self.clicked = clicked
+ self.hovered = hovered
+ self.unhovered = unhovered
+
+ self.child = None
+
+ # Add us to a drag group on creation.
+ if drag_name:
+ self.drag_group = default_drag_group()
+
+ # The current x and y coordinates of this displayable.
+ self.x = None
+ self.y = None
+
+ # The width and height of the child.
+ self.w = None
+ self.h = None
+
+ # The width and height of our parent.
+ self.parent_width = None
+ self.parent_height = None
+
+ # The target x and y coordinates of this displayable. (The
+ # coordinates that we're snapping to.)
+ self.target_x = None
+ self.target_y = None
+
+ # The offset from the location of the mouse to the "grab point",
+ # which is where the things that are being moved are offset from.
+ self.grab_x = None
+ self.grab_y = None
+
+ # x and y from the last time we rendered.
+ self.last_x = None
+ self.last_y = None
+
+ # The abs_x and abs_y from when we started the grab.
+ self.start_x = 0
+ self.start_y = 0
+
+ # The last time we were shown, using the animation timebases.
+ self.at = 0
+
+ # The (animation timebase) time at which we should reach
+ # the target coordinates.
+ self.target_at = 0
+
+ # The displayable we were last dropping on.
+ self.last_drop = None
+
+ # Did we move over the course of this drag?
+ self.drag_moved = False
+
+ if replaces is not None:
+ self.x = replaces.x
+ self.y = replaces.y
+ self.at = replaces.at
+ self.target_x = replaces.target_x
+ self.target_y = replaces.target_y
+ self.target_at = replaces.target_at
+
+ if d is not None:
+ self.add(d)
+
+
+ def snap(self, x, y, delay=0):
+ """
+ :doc: drag_drop method
+
+ Changes the position of the drag. If the drag is not showing,
+ then the position change is instantaneous. Otherwise, the
+ position change takes `delay` seconds, and is animated as a
+ linear move.
+ """
+
+ self.target_x = x
+ self.target_y = y
+
+ if self.x is not None:
+ self.target_at = self.at + delay
+ else:
+ self.target_at = self.at
+ self.x = x
+ self.y = y
+
+ redraw(self, 0)
+
+ def set_style_prefix(self, prefix, root):
+ super(Drag, self).set_style_prefix(prefix, root)
+
+ if self.child is not None:
+ self.child.set_style_prefix(prefix, False)
+
+ def add(self, d):
+ if self.child is not None:
+ raise Exception("Drag expects either zero or one children.")
+
+ self.child = renpy.easy.displayable(d)
+
+ def set_child(self, d):
+ """
+ :doc: drag_drop method
+
+ Changes the child of this drag to `d`.
+ """
+
+ d.per_interact()
+ self.child = renpy.easy.displayable(d)
+
+ def top(self):
+ """
+ :doc: drag_drop method
+
+ Raises this displayable to the top of its drag_group.
+ """
+
+ if self.drag_group is not None:
+ self.drag_group.raise_children([ self ])
+
+ def visit(self):
+ return [ self.child ]
+
+ def focus(self, default=False):
+ super(Drag, self).focus(default)
+
+ rv = None
+
+ if not default:
+ rv = run(self.hovered)
+
+ return rv
+
+ def unfocus(self, default=False):
+ super(Drag, self).unfocus(default)
+
+ if not default:
+ run(self.unhovered)
+
+ def render(self, width, height, st, at):
+
+ child = self.style.child
+ if child is None:
+ child = self.child
+
+ self.parent_width = width
+ self.parent_height = height
+
+ cr = render(child, width, height, st, at)
+ cw, ch = cr.get_size()
+
+ rv = Render(cw, ch)
+ rv.blit(cr, (0, 0))
+
+ self.w = cw
+ self.h = ch
+
+ # If we don't have a position, then look for it in a drag group.
+ if (self.x is None) and (self.drag_group is not None) and (self.drag_name is not None):
+ if self.drag_name in self.drag_group.positions:
+ self.x, self.y = self.drag_group.positions[self.drag_name]
+
+ # If we don't have a position, run the placement code and use
+ # that to compute our placement.
+ if self.x is None:
+ self.x, self.y = self.place(None, 0, 0, width, height, rv)
+ self.x = int(self.x)
+ self.y = int(self.y)
+
+ if self.target_x is None:
+ self.target_x = self.x
+ self.target_y = self.y
+ self.target_at = at
+
+ # Determine if we need to do the snap animation.
+ if at >= self.target_at:
+ self.x = self.target_x
+ self.y = self.target_y
+ else:
+ done = (at - self.at) / (self.target_at - self.at)
+ self.x = absolute(self.x + done * (self.target_x - self.x))
+ self.y = absolute(self.y + done * (self.target_y - self.y))
+ redraw(self, 0)
+
+ if self.draggable or self.clicked is not None:
+
+ fx, fy, fw, fh = self.drag_handle
+
+ if isinstance(fx, float):
+ fx = int(fx * cw)
+
+ if isinstance(fy, float):
+ fy = int(fy * ch)
+
+ if isinstance(fw, float):
+ fw = int(fw * cw)
+
+ if isinstance(fh, float):
+ fh = int(fh * ch)
+
+ rv.add_focus(self, None, fx, fy, fw, fh, fx, fy, cr.subsurface((fx, fy, fw, fh)))
+
+ self.last_x = self.x
+ self.last_y = self.y
+ self.at = at
+
+ return rv
+
+ def event(self, ev, x, y, st):
+
+ if not self.is_focused():
+ return self.child.event(ev, x, y, st)
+
+ # if not self.draggable:
+ # return self.child.event(ev, x, y, st)
+
+ # Mouse, in parent-relative coordinates.
+ par_x = self.last_x + x
+ par_y = self.last_y + y
+
+ grabbed = (renpy.display.focus.get_grab() is self)
+
+ if grabbed:
+ joined_offsets = self.drag_joined(self)
+ joined = [ i[0] for i in joined_offsets ]
+
+ elif self.draggable and map_event(ev, "drag_activate"):
+
+ joined_offsets = self.drag_joined(self)
+ joined = [ i[0] for i in joined_offsets ]
+
+ if not joined:
+ raise renpy.display.core.IgnoreEvent()
+
+ renpy.display.focus.set_grab(self)
+
+ self.grab_x = x
+ self.grab_y = y
+
+ # If we're not the only thing we're joined with, we
+ # might need to adjust our grab point.
+ for i, xo, yo in joined_offsets:
+ if i is self:
+ self.grab_x += xo
+ self.grab_y += yo
+ break
+
+ self.drag_moved = False
+ self.start_x = par_x
+ self.start_y = par_y
+
+ grabbed = True
+
+ # Handle clicking on droppables.
+ if not grabbed:
+ if self.clicked is not None and map_event(ev, "drag_deactivate"):
+ rv = run(self.clicked)
+ if rv is not None:
+ return rv
+
+ raise renpy.display.core.IgnoreEvent()
+
+ return self.child.event(ev, x, y, st)
+
+ # Handle moves by moving things relative to the grab point.
+ if ev.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+
+ if not self.drag_moved and (self.start_x != par_x or self.start_y != par_y):
+ self.drag_moved = True
+
+ # We may not be in the drag_joined group.
+ self.set_style_prefix("idle_", True)
+
+ # Set the style.
+ for i in joined:
+ i.set_style_prefix("selected_hover_", True)
+
+ # Raise the joined items.
+ if self.drag_raise and self.drag_group is not None:
+ self.drag_group.raise_children(joined)
+
+ if self.drag_moved:
+ for i, xo, yo in joined_offsets:
+
+ new_x = par_x - self.grab_x + xo
+ new_y = par_y - self.grab_y + yo
+ new_x = max(new_x, 0)
+ new_x = min(new_x, i.parent_width - i.w)
+ new_y = max(new_y, 0)
+ new_y = min(new_y, i.parent_height - i.h)
+
+ if i.drag_group is not None and i.drag_name is not None:
+ i.drag_group.positions[i.drag_name] = (new_x, new_y)
+
+ i.x = new_x
+ i.y = new_y
+ i.target_x = new_x
+ i.target_y = new_y
+ i.target_at = self.at
+ redraw(i, 0)
+
+ if (self.drag_group is not None) and self.drag_moved:
+ drop = self.drag_group.get_best_drop(joined)
+ else:
+ drop = None
+
+ if drop is not self.last_drop:
+
+ if self.last_drop is not None:
+ self.last_drop.set_style_prefix("idle_", True)
+
+ if drop is not None:
+ drop.set_style_prefix("selected_idle_", True)
+
+ self.last_drop = drop
+
+ if map_event(ev, 'drag_deactivate'):
+ renpy.display.focus.set_grab(None)
+
+ if drop is not None:
+ drop.set_style_prefix("idle_", True)
+
+ for i in joined:
+ i.set_style_prefix("idle_", True)
+
+ self.set_style_prefix("hover_", True)
+
+ self.grab_x = None
+ self.grab_y = None
+ self.last_drop = None
+
+ if self.drag_moved:
+
+ # Call the drag callback.
+ drag = joined[0]
+ if drag.dragged is not None:
+ rv = run(drag.dragged, joined, drop)
+ if rv is not None:
+ return rv
+
+ # Call the drop callback.
+ if drop is not None and drop.dropped is not None:
+ rv = run(drop.dropped, drop, joined)
+ if rv is not None:
+ return rv
+
+ else:
+
+ # Call the clicked callback.
+ if self.clicked:
+ rv = run(self.clicked)
+ if rv is not None:
+ return rv
+
+ raise renpy.display.core.IgnoreEvent()
+
+
+ def get_placement(self):
+
+ if self.x is not None:
+ return self.x, self.y, 0, 0, 0, 0, True
+ else:
+ return super(Drag, self).get_placement()
+
+ def per_interact(self):
+ self.set_style_prefix("idle_", True)
+ super(Drag, self).per_interact()
+
+
+class DragGroup(renpy.display.layout.MultiBox):
+ """
+ :doc: drag_drop class
+
+ Represents a group of Drags. A Drag is limited to the boundary of
+ its DragGroup. Dropping only works between Drags that are in the
+ same DragGroup. Drags may only be raised when they are inside a
+ DragGroup.
+
+ A DragGroup is laid out like a :func:`Fixed`.
+
+ All positional parameters to the DragGroup constructor should be
+ Drags, that are added to the DragGroup.
+ """
+
+ _list_type = renpy.python.RevertableList
+
+ def __init__(self, *children, **properties):
+ properties.setdefault("style", "fixed")
+ properties.setdefault("layout", "fixed")
+
+ replaces = properties.pop("replaces", None)
+
+ super(DragGroup, self).__init__(**properties)
+
+ if replaces is not None:
+ self.positions = renpy.python.RevertableDict(replaces.positions)
+ self.sensitive = replaces.sensitive
+ else:
+ self.positions = renpy.python.RevertableDict()
+ self.sensitive = True
+
+ for i in children:
+ self.add(i)
+
+
+ def add(self, child):
+ """
+ :doc: drag_drop method
+
+ Adds `child`, which must be a Drag, to this DragGroup.
+ """
+
+ if not isinstance(child, Drag):
+ raise Exception("Only drags can be added to a drag group.")
+
+ child.drag_group = self
+ super(DragGroup, self).add(child)
+
+ def remove(self, child):
+ """
+ :doc: drag_drop method
+
+ Removes `child` from this DragGroup.
+ """
+
+
+ if not isinstance(child, Drag):
+ raise Exception("Only drags can be removed from a drag group.")
+
+ child.x = None
+ super(DragGroup, self).remove(child)
+
+
+ def event(self, ev, x, y, st):
+
+ if not self.sensitive:
+ return None
+
+ return super(DragGroup, self).event(ev, x, y, st)
+
+ def raise_children(self, l):
+ """
+ Raises the children in `l` to the top of this drag_group, using the
+ order given in l for those children.
+ """
+
+ s = set(l)
+
+ offset_map = { }
+
+ children = [ ]
+ offsets = [ ]
+
+ for i, c in enumerate(self.children):
+ if i < len(self.offsets):
+ o = self.offsets[i]
+ else:
+ o = (0, 0)
+
+ if c not in s:
+ children.append(c)
+ offsets.append(o)
+ else:
+ offset_map[c] = o
+
+ for c in l:
+ if c in offset_map:
+ children.append(c)
+ offsets.append(offset_map[c])
+
+ self.children = self._list_type(children)
+ self.offsets = self._list_type(offsets)
+
+
+ def get_best_drop(self, joined):
+ """
+ Returns the droppable that the members of joined overlap the most.
+ """
+
+ max_overlap = 0
+ rv = 0
+
+ joined_set = set(joined)
+
+ for d in joined:
+
+ r1 = (d.x, d.y, d.w, d.h)
+
+ for c in self.children:
+ if c in joined_set:
+ continue
+
+ if not c.droppable:
+ continue
+
+ r2 = (c.x, c.y, c.w, c.h)
+
+ overlap = rect_overlap_area(r1, r2)
+
+ if overlap >= max_overlap:
+ rv = c
+ max_overlap = overlap
+
+ if max_overlap <= 0:
+ return None
+ else:
+ return rv
+
+ def get_children(self):
+ """
+ Returns a list of Drags that are the children of
+ this DragGroup.
+ """
+
+ return renpy.python.RevertableList(self.children)
+
+ def get_child_by_name(self, name):
+ """
+ :doc: drag_drop method
+
+ Returns the first child of this DragGroup that has a drag_name
+ of name.
+ """
+
+ for i in self.children:
+ if i.drag_name == name:
+ return i
+
+ return None
+
+
+def rect_overlap_area(r1, r2):
+ """
+ Returns the number of pixels by which rectangles r1 and r2 overlap.
+ """
+
+ x1, y1, w1, h1 = r1
+ x2, y2, w2, h2 = r2
+
+ maxleft = max(x1, x2)
+ minright = min(x1 + w1, x2 + w2)
+ maxtop = max(y1, y2)
+ minbottom = min(y1 + h1, y2 + h2)
+
+ if minright < maxleft:
+ return 0
+
+ if minbottom < maxtop:
+ return 0
+
+ return (minright - maxleft) * (minbottom - maxtop)
+
+
diff --git a/unrpyc/renpy/display/error.py b/unrpyc/renpy/display/error.py
new file mode 100644
index 0000000..a8f7e1b
--- /dev/null
+++ b/unrpyc/renpy/display/error.py
@@ -0,0 +1,159 @@
+# 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 code to handle GUI-based error reporting.
+
+import renpy.display
+import os
+
+error_handled = False
+
+##############################################################################
+# Initialized approach.
+
+def call_exception_screen(screen_name, **kwargs):
+ try:
+
+ old_quit = renpy.config.quit_action
+ renpy.config.quit_action = renpy.exports.quit
+
+ for i in renpy.config.layers:
+ renpy.game.context().scene_lists.clear(i)
+
+ renpy.exports.show_screen(screen_name, _transient=True, **kwargs)
+ return renpy.ui.interact(mouse="screen", type="screen", suppress_overlay=True, suppress_underlay=True)
+
+ finally:
+ renpy.config.quit_action = old_quit
+
+def rollback_action():
+ renpy.exports.rollback(force=True)
+
+def init_display():
+ """
+ The minimum amount of code required to init the display.
+ """
+
+ if not renpy.game.interface:
+ renpy.display.core.Interface()
+ renpy.loader.index_archives()
+ renpy.display.im.cache.init()
+ renpy.style.styles_built = True # The styles we use were built in renpy.main.
+
+ renpy.ui.reset()
+
+def error_dump():
+ """
+ Handles dumps in the case where an error occurs.
+ """
+
+ renpy.dump.dump(True)
+
+def report_exception(short, full, traceback_fn):
+ """
+ Reports an exception to the user. Returns True if the exception should
+ be raised by the normal reporting mechanisms. Otherwise, should raise
+ the appropriate exception to cause a reload or quit or rollback.
+ """
+
+ global error_handled
+ error_handled = True
+
+ error_dump()
+
+ if renpy.game.args.command != "run": #@UndefinedVariable
+ return True
+
+ if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
+ return True
+
+ if not renpy.exports.has_screen("_exception"):
+ return True
+
+ try:
+ init_display()
+ except:
+ return True
+
+ if renpy.display.draw is None:
+ return True
+
+ ignore_action = None
+ rollback_action = None
+ reload_action = None
+
+ try:
+ if not renpy.game.context().init_phase:
+
+ if renpy.config.rollback_enabled:
+ rollback_action = renpy.display.error.rollback_action
+
+ reload_action = renpy.exports.curried_call_in_new_context("_save_reload_game")
+
+ if renpy.game.context(-1).next_node is not None:
+ ignore_action = renpy.ui.returns(False)
+ except:
+ pass
+
+ renpy.game.invoke_in_new_context(
+ call_exception_screen,
+ "_exception",
+ short=short, full=full,
+ rollback_action=rollback_action,
+ reload_action=reload_action,
+ ignore_action=ignore_action,
+ traceback_fn=traceback_fn,
+ )
+
+
+def report_parse_errors(errors, error_fn):
+ """
+ Reports an exception to the user. Returns True if the exception should
+ be raised by the normal reporting mechanisms. Otherwise, should raise
+ the appropriate exception.
+ """
+
+ global error_handled
+ error_handled = True
+
+ error_dump()
+
+ if renpy.game.args.command != "run": #@UndefinedVariable
+ return True
+
+ if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
+ return True
+
+ if not renpy.exports.has_screen("_parse_errors"):
+ return True
+
+ init_display()
+
+ reload_action = renpy.exports.utter_restart
+
+ renpy.game.invoke_in_new_context(
+ call_exception_screen,
+ "_parse_errors",
+ reload_action=reload_action,
+ errors=errors,
+ error_fn = error_fn,
+ )
+
diff --git a/unrpyc/renpy/display/focus.py b/unrpyc/renpy/display/focus.py
new file mode 100644
index 0000000..521fe22
--- /dev/null
+++ b/unrpyc/renpy/display/focus.py
@@ -0,0 +1,449 @@
+# 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 code to manage focus on the display.
+
+import pygame
+import renpy.display
+
+class Focus(object):
+
+ def __init__(self, widget, arg, x, y, w, h):
+
+ self.widget = widget
+ self.arg = arg
+ self.x = x
+ self.y = y
+ self.w = w
+ self.h = h
+
+ def copy(self):
+ return Focus(
+ self.widget,
+ self.arg,
+ self.x,
+ self.y,
+ self.w,
+ self.h)
+
+ def __repr__(self):
+ return "<Focus: %r %r (%r, %r, %r, %r)>" % (
+ self.widget,
+ self.arg,
+ self.x,
+ self.y,
+ self.w,
+ self.h)
+
+
+# The current focus argument.
+argument = None
+
+# The widget currently grabbing the input, if any.
+grab = None
+
+# The default focus for the current screen.
+default_focus = None
+
+# Sets the currently focused widget.
+def set_focused(widget, arg):
+ global argument
+ argument = arg
+ renpy.game.context().scene_lists.focused = widget
+
+# Gets the currently focused widget.
+def get_focused():
+ return renpy.game.context().scene_lists.focused
+
+# Get the mouse cursor for the focused widget.
+def get_mouse():
+ focused = get_focused()
+ if focused is None:
+ return None
+ else:
+ return focused.style.mouse
+
+def set_grab(widget):
+ global grab
+ grab = widget
+
+def get_grab():
+ return grab
+
+# The current list of focuses that we know about.
+focus_list = [ ]
+
+# This takes in a focus list from the rendering system.
+def take_focuses():
+ global focus_list
+ focus_list = [ ]
+
+ renpy.display.render.take_focuses(focus_list)
+
+ global default_focus
+ default_focus = None
+
+ for f in focus_list:
+ if f.x is None:
+ default_focus = f
+
+def focus_coordinates():
+ """
+ :doc: other
+
+ This attempts to find the coordinates of the currently-focused
+ displayable. If it can, it will return them as a (x, y, w, h)
+ tuple. If not, it will return a (None, None, None, None) tuple.
+ """
+
+ current = get_focused()
+
+ for i in focus_list:
+ if i.widget == current and i.arg == argument:
+ return i.x, i.y, i.w, i.h
+
+ return None, None, None, None
+
+
+# This is called before each interaction. It's purpose is to choose
+# the widget that is focused, and to mark it as focused and all of
+# the other widgets as unfocused.
+
+# The new grab widget. (The one that replaced the old grab widget at the start
+# of the interaction.)
+new_grab = None
+
+def before_interact(roots):
+
+ global new_grab
+ global grab
+
+ # a list of focusable, name tuples.
+ fwn = [ ]
+
+ def callback(f, n):
+ fwn.append((f, n))
+
+ for root in roots:
+ root.find_focusable(callback, None)
+
+ # Assign a full name to each focusable.
+
+ namecount = { }
+
+ for f, n in fwn:
+ serial = namecount.get(n, 0)
+ namecount[n] = serial + 1
+
+ f.full_focus_name = n, serial
+
+ # If there's something with the same full name as the current widget,
+ # it becomes the new current widget.
+
+ current = get_focused()
+
+ if current is not None:
+ current_name = current.full_focus_name
+
+ for f, n in fwn:
+ if f.full_focus_name == current_name:
+ current = f
+ set_focused(f, None)
+ break
+ else:
+ current = None
+
+ # Otherwise, focus the default widget, or nothing.
+ if current is None:
+
+ for f, n in fwn:
+ if f.default:
+ current = f
+ set_focused(f, None)
+ break
+ else:
+ set_focused(None, None)
+
+ # Finally, mark the current widget as the focused widget, and
+ # all other widgets as unfocused.
+ for f, n in fwn:
+ if f is not current:
+ f.unfocus(default=True)
+
+ if current:
+ current.focus(default=True)
+
+ grab = new_grab
+ new_grab = None
+
+# This changes the focus to be the widget contained inside the new
+# focus object.
+def change_focus(newfocus, default=False):
+ rv = None
+
+ if grab:
+ return
+
+ if newfocus is None:
+ widget = None
+ else:
+ widget = newfocus.widget
+
+ current = get_focused()
+
+ # Nothing to do.
+ if current is widget and (newfocus is None or newfocus.arg == argument):
+ return rv
+
+ if current is not None:
+ current.unfocus(default=default)
+
+ current = widget
+
+ if newfocus is not None:
+ arg = newfocus.arg
+ else:
+ arg = None
+
+ set_focused(current, arg)
+
+ if widget is not None:
+ rv = widget.focus(default=default)
+
+ return rv
+
+# This handles mouse events, to see if they change the focus.
+def mouse_handler(ev, x, y, default=False):
+
+ if ev.type not in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+ return
+
+ new_focus = renpy.display.render.focus_at_point(x, y)
+
+ if new_focus is None:
+ new_focus = default_focus
+
+ return change_focus(new_focus, default=default)
+
+
+# This focuses an extreme widget, which is one of the widgets that's
+# at an edge. To do this, we multiply the x, y, width, and height by
+# the supplied multiplers, add them all up, and take the focus with
+# the largest value.
+def focus_extreme(xmul, ymul, wmul, hmul):
+
+ max_focus = None
+ max_score = -(65536**2)
+
+ for f in focus_list:
+
+ if not f.x:
+ continue
+
+ score = (f.x * xmul +
+ f.y * ymul +
+ f.w * wmul +
+ f.h * hmul)
+
+ if score > max_score:
+ max_score = score
+ max_focus = f
+
+ if max_focus:
+ return change_focus(max_focus)
+
+
+# This calculates the distance between two points, applying
+# the given fudge factors. The distance is left squared.
+def points_dist(x0, y0, x1, y1, xfudge, yfudge):
+ return (( x0 - x1 ) * xfudge ) ** 2 + \
+ (( y0 - y1 ) * yfudge ) ** 2
+
+
+# This computes the distance between two horizontal lines. (So the
+# distance is either vertical, or has a vertical component to it.)
+#
+# The distance is left squared.
+def horiz_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
+
+ # The lines overlap in x.
+ if bx0 <= ax0 <= ax1 <= bx1 or \
+ ax0 <= bx0 <= bx1 <= ax1 or \
+ ax0 <= bx0 <= ax1 <= bx1 or \
+ bx0 <= ax0 <= bx1 <= ax1:
+ return (ay0 - by0) ** 2
+
+ # The right end of a is to the left of the left end of b.
+ if ax0 <= ax1 <= bx0 <= bx1:
+ return points_dist(ax1, ay1, bx0, by0, renpy.config.focus_crossrange_penalty, 1.0)
+
+ if bx0 <= bx1 <= ax0 <= ax1:
+ return points_dist(ax0, ay0, bx1, by1, renpy.config.focus_crossrange_penalty, 1.0)
+
+ assert False
+
+# This computes the distance between two vertical lines. (So the
+# distance is either hortizontal, or has a horizontal component to it.)
+#
+# The distance is left squared.
+def verti_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
+
+ # The lines overlap in x.
+ if by0 <= ay0 <= ay1 <= by1 or \
+ ay0 <= by0 <= by1 <= ay1 or \
+ ay0 <= by0 <= ay1 <= by1 or \
+ by0 <= ay0 <= by1 <= ay1:
+ return (ax0 - bx0) ** 2
+
+ # The right end of a is to the left of the left end of b.
+ if ay0 <= ay1 <= by0 <= by1:
+ return points_dist(ax1, ay1, bx0, by0, 1.0, renpy.config.focus_crossrange_penalty)
+
+ if by0 <= by1 <= ay0 <= ay1:
+ return points_dist(ax0, ay0, bx1, by1, 1.0, renpy.config.focus_crossrange_penalty)
+
+ assert False
+
+
+
+# This focuses the widget that is nearest to the current widget. To
+# determine nearest, we compute points on the widgets using the
+# {from,to}_{x,y}off values. We pick the nearest, applying a fudge
+# multiplier to the distances in each direction, that satisfies
+# the condition (which is given a Focus object to evaluate).
+#
+# If no focus can be found matching the above, we look for one
+# with an x of None, and make that the focus. Otherwise, we do
+# nothing.
+#
+# If no widget is focused, we pick one and focus it.
+#
+# If the current widget has an x of None, we pass things off to
+# focus_extreme to deal with.
+def focus_nearest(from_x0, from_y0, from_x1, from_y1,
+ to_x0, to_y0, to_x1, to_y1,
+ line_dist,
+ condition,
+ xmul, ymul, wmul, hmul):
+
+ if not focus_list:
+ return
+
+ # No widget focused.
+ current = get_focused()
+
+ if not current:
+ change_focus(focus_list[0])
+ return
+
+ # Find the current focus.
+ for f in focus_list:
+ if f.widget is current and f.arg == argument:
+ from_focus = f
+ break
+ else:
+ # If we can't pick something.
+ change_focus(focus_list[0])
+ return
+
+ # If placeless, focus_extreme.
+ if from_focus.x is None:
+ focus_extreme(xmul, ymul, wmul, hmul)
+ return
+
+ fx0 = from_focus.x + from_focus.w * from_x0
+ fy0 = from_focus.y + from_focus.h * from_y0
+ fx1 = from_focus.x + from_focus.w * from_x1
+ fy1 = from_focus.y + from_focus.h * from_y1
+
+ placeless = None
+ new_focus = None
+
+ # a really big number.
+ new_focus_dist = (65536.0 * renpy.config.focus_crossrange_penalty) ** 2
+
+ for f in focus_list:
+
+ if f is from_focus:
+ continue
+
+ if f.x is None:
+ placeless = f
+ continue
+
+ if not condition(from_focus, f):
+ continue
+
+ tx0 = f.x + f.w * to_x0
+ ty0 = f.y + f.h * to_y0
+ tx1 = f.x + f.w * to_x1
+ ty1 = f.y + f.h * to_y1
+
+ dist = line_dist(fx0, fy0, fx1, fy1,
+ tx0, ty0, tx1, ty1)
+
+ if dist < new_focus_dist:
+ new_focus = f
+ new_focus_dist = dist
+
+ # If we couldn't find anything, try the placeless focus.
+ new_focus = new_focus or placeless
+
+ # If we have something, switch to it.
+ if new_focus:
+ return change_focus(new_focus)
+
+ # And, we're done.
+
+
+
+def key_handler(ev):
+
+ if renpy.display.behavior.map_event(ev, 'focus_right'):
+ return focus_nearest(0.9, 0.1, 0.9, 0.9,
+ 0.1, 0.1, 0.1, 0.9,
+ verti_line_dist,
+ lambda old, new : old.x + old.w <= new.x,
+ -1, 0, 0, 0)
+
+ if renpy.display.behavior.map_event(ev, 'focus_left'):
+ return focus_nearest(0.1, 0.1, 0.1, 0.9,
+ 0.9, 0.1, 0.9, 0.9,
+ verti_line_dist,
+ lambda old, new : new.x + new.w <= old.x,
+ 1, 0, 1, 0)
+
+ if renpy.display.behavior.map_event(ev, 'focus_up'):
+ return focus_nearest(0.1, 0.1, 0.9, 0.1,
+ 0.1, 0.9, 0.9, 0.9,
+ horiz_line_dist,
+ lambda old, new : new.y + new.h <= old.y,
+ 0, 1, 0, 1)
+
+ if renpy.display.behavior.map_event(ev, 'focus_down'):
+ return focus_nearest(0.1, 0.9, 0.9, 0.9,
+ 0.1, 0.1, 0.9, 0.1,
+ horiz_line_dist,
+ lambda old, new : old.y + old.h <= new.y,
+ 0, -1, 0, 0)
+
+
+
diff --git a/unrpyc/renpy/display/im.py b/unrpyc/renpy/display/im.py
new file mode 100644
index 0000000..d90fb51
--- /dev/null
+++ b/unrpyc/renpy/display/im.py
@@ -0,0 +1,1562 @@
+# 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 the new image code, which includes provisions for
+# size-based caching and constructing images from operations (like
+# cropping and scaling).
+
+import renpy.display
+
+import math
+import zipfile
+import io
+import threading
+import time
+
+
+# This is an entry in the image cache.
+class CacheEntry(object):
+
+ def __init__(self, what, surf):
+
+ # The object that is being cached (which needs to be
+ # hashable and comparable).
+ self.what = what
+
+ # The pygame surface corresponding to the cached object.
+ self.surf = surf
+
+ # The size of this image.
+ w, h = surf.get_size()
+ self.size = w * h
+
+ # The time when this cache entry was last used.
+ self.time = 0
+
+# This is the singleton image cache.
+class Cache(object):
+
+ def __init__(self):
+
+ # The current arbitrary time. (Increments by one for each
+ # interaction.)
+ self.time = 0
+
+ # A map from Image object to CacheEntry.
+ self.cache = { }
+
+ # A list of Image objects that we want to preload.
+ self.preloads = [ ]
+
+ # False if this is not the first preload in this tick.
+ self.first_preload_in_tick = True
+
+ # The total size of the current generation of images.
+ self.size_of_current_generation = 0
+
+ # The total size of everything in the cache.
+ self.total_cache_size = 0
+
+ # A lock that must be held when updating the cache.
+ self.lock = threading.Condition()
+
+ # A lock that mist be held to notify the preload thread.
+ self.preload_lock = threading.Condition()
+
+ # Is the preload_thread alive?
+ self.keep_preloading = True
+
+ # A map from image object to surface, only for objects that have
+ # been pinned into memory.
+ self.pin_cache = { }
+
+ # Images that we tried, and failed, to preload.
+ self.preload_blacklist = set()
+
+ # The size of the cache, in pixels.
+ self.cache_limit = 0
+
+ # The preload thread.
+ self.preload_thread = threading.Thread(target=self.preload_thread_main, name="preloader")
+ self.preload_thread.setDaemon(True)
+ self.preload_thread.start()
+
+ # Have we been added this tick?
+ self.added = set()
+
+ # A list of (time, filename, preload) tuples. This is updated when
+ # config.developer is True and an image is loaded. Preload is a
+ # flag that is true if the image was loaded from the preload
+ # thread. The log is limited to 100 entries, and the newest entry
+ # is first.
+ #
+ # This is only updated when config.developer is True.
+ self.load_log = [ ]
+
+
+ def init(self):
+ """
+ Updates the cache object to make use of settings that might be provided
+ by the game-maker.
+ """
+
+ self.cache_limit = renpy.config.image_cache_size * renpy.config.screen_width * renpy.config.screen_height
+
+ def quit(self): #@ReservedAssignment
+ if not self.preload_thread.isAlive():
+ return
+
+ with self.preload_lock:
+ self.keep_preloading = False
+ self.preload_lock.notify()
+
+ self.preload_thread.join()
+
+
+ # Clears out the cache.
+ def clear(self):
+
+ self.lock.acquire()
+
+ self.preloads = [ ]
+ self.pin_cache = { }
+ self.cache = { }
+ self.first_preload_in_tick = True
+ self.size_of_current_generation = 0
+ self.total_cache_size = 0
+
+ self.added.clear()
+
+ self.lock.release()
+
+ # Increments time, and clears the list of images to be
+ # preloaded.
+ def tick(self):
+
+ with self.lock:
+ self.time += 1
+ self.preloads = [ ]
+ self.first_preload_in_tick = True
+ self.size_of_current_generation = 0
+ self.added.clear()
+
+ if renpy.config.debug_image_cache:
+ renpy.display.ic_log.write("----")
+ filename, line = renpy.exports.get_filename_line()
+ renpy.display.ic_log.write("%s %d", filename, line)
+
+ # The preload thread can deal with this update, so we don't need
+ # to lock things.
+ def end_tick(self):
+ self.preloads = [ ]
+
+
+ # This returns the pygame surface corresponding to the provided
+ # image. It also takes care of updating the age of images in the
+ # cache to be current, and maintaining the size of the current
+ # generation of images.
+ def get(self, image, predict=False):
+
+ if not isinstance(image, ImageBase):
+ raise Exception("Expected an image of some sort, but got" + str(image) + ".")
+
+ if not image.cache:
+ surf = image.load()
+ renpy.display.render.mutated_surface(surf)
+ return surf
+
+ # First try to grab the image out of the cache without locking it.
+ ce = self.cache.get(image, None)
+
+ # Otherwise, we load the image ourselves.
+ if ce is None:
+
+ try:
+ if image in self.pin_cache:
+ surf = self.pin_cache[image]
+ else:
+ surf = image.load()
+
+ except:
+ raise
+
+ with self.lock:
+
+ ce = CacheEntry(image, surf)
+
+ if image not in self.cache:
+ self.total_cache_size += ce.size
+
+ self.cache[image] = ce
+
+ # Indicate that this surface had changed.
+ renpy.display.render.mutated_surface(ce.surf)
+
+ if renpy.config.debug_image_cache:
+ if predict:
+ renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.total_cache_size / self.cache_limit)
+ else:
+ renpy.display.ic_log.write("Total Miss %r", ce.what)
+
+ renpy.display.draw.load_texture(ce.surf)
+
+
+ # Move it into the current generation. This isn't protected by
+ # a lock, so in certain circumstances we could have an
+ # inaccurate size - but that will be cured at the end of the
+ # current generation.
+
+ if ce.time != self.time:
+ ce.time = self.time
+ self.size_of_current_generation += ce.size
+
+ # Done... return the surface.
+ return ce.surf
+
+
+ # This kills off a given cache entry.
+ def kill(self, ce):
+
+ # Should never happen... but...
+ if ce.time == self.time:
+ self.size_of_current_generation -= ce.size
+
+ self.total_cache_size -= ce.size
+ del self.cache[ce.what]
+
+ if renpy.config.debug_image_cache:
+ renpy.display.ic_log.write("Removed %r", ce.what)
+
+ def cleanout(self):
+ """
+ Cleans out the cache, if it's gotten too large. Returns True
+ if the cache is smaller than the size limit, or False if it's
+ bigger and we don't want to continue preloading.
+ """
+
+ # If we're within the limit, return.
+ if self.total_cache_size <= self.cache_limit:
+ return True
+
+ # If we're outside the cache limit, we need to go and start
+ # killing off some of the entries until we're back inside it.
+
+ for ce in sorted(iter(self.cache.values()), key=lambda a : a.time):
+
+ if ce.time == self.time:
+ # If we're bigger than the limit, and there's nothing
+ # to remove, we should stop the preloading right away.
+ return False
+
+ # Otherwise, kill off the given cache entry.
+ self.kill(ce)
+
+ # If we're in the limit, we're done.
+ if self.total_cache_size <= self.cache_limit:
+ break
+
+ return True
+
+
+ # Called to report that a given image would like to be preloaded.
+ def preload_image(self, im):
+
+ if not isinstance(im, ImageBase):
+ return
+
+ with self.lock:
+
+ if im in self.added:
+ return
+
+ self.added.add(im)
+
+ if im in self.cache:
+ self.get(im)
+ in_cache = True
+ else:
+ self.preloads.append(im)
+ in_cache = False
+
+ if not in_cache:
+
+ with self.preload_lock:
+ self.preload_lock.notify()
+
+ if in_cache and renpy.config.debug_image_cache:
+ renpy.display.ic_log.write("Kept %r", im)
+
+
+ def start_prediction(self):
+ """
+ Called at the start of prediction, to ensure the thread runs
+ at least once to clean out the cache.
+ """
+
+ with self.preload_lock:
+ self.preload_lock.notify()
+
+ def preload_thread_main(self):
+
+ while self.keep_preloading:
+
+ self.preload_lock.acquire()
+ self.preload_lock.wait()
+ self.preload_lock.release()
+
+ while self.preloads and self.keep_preloading:
+
+ start = time.time()
+
+ # If the size of the current generation is bigger than the
+ # total cache size, stop preloading.
+ with self.lock:
+
+ # If the cache is overfull, clean it out.
+ if not self.cleanout():
+
+ if renpy.config.debug_image_cache:
+ for i in self.preloads:
+ renpy.display.ic_log.write("Overfull %r", i)
+
+ self.preloads = [ ]
+
+ break
+
+ try:
+ image = self.preloads.pop(0)
+
+ if image not in self.preload_blacklist:
+ try:
+ self.get(image, True)
+ except:
+ self.preload_blacklist.add(image)
+ except:
+ pass
+
+ with self.lock:
+ self.cleanout()
+
+ # If we have time, preload pinned images.
+ if self.keep_preloading and not renpy.game.less_memory:
+
+ workset = set(renpy.store._cache_pin_set)
+
+ # Remove things that are not in the workset from the pin cache,
+ # and remove things that are in the workset from pin cache.
+ for i in list(self.pin_cache.keys()):
+
+ if i in workset:
+ workset.remove(i)
+ else:
+ surf = self.pin_cache[i]
+
+ del self.pin_cache[i]
+
+
+ # For each image in the worklist...
+ for image in workset:
+
+ if image in self.preload_blacklist:
+ continue
+
+ # If we have normal preloads, break out.
+ if self.preloads:
+ break
+
+ try:
+ surf = image.load()
+ self.pin_cache[image] = surf
+ renpy.display.draw.load_texture(surf)
+ except:
+ self.preload_blacklist.add(image)
+
+ def add_load_log(self, filename):
+
+ if not renpy.config.developer:
+ return
+
+ preload = (threading.current_thread() is self.preload_thread)
+
+ self.load_log.insert(0, (time.time(), filename, preload))
+
+ while len(self.load_log) > 100:
+ self.load_log.pop()
+
+
+
+# The cache object.
+cache = Cache()
+
+def free_memory():
+ """
+ Frees some memory.
+ """
+
+ renpy.display.draw.free_memory()
+ cache.clear()
+
+
+class ImageBase(renpy.display.core.Displayable):
+ """
+ This is the base class for all of the various kinds of images that
+ we can possibly have.
+ """
+
+ __version__ = 1
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.cache = True
+
+ def __init__(self, *args, **properties):
+
+ self.rle = properties.pop('rle', None)
+ self.cache = properties.pop('cache', True)
+
+ properties.setdefault('style', 'image')
+
+ super(ImageBase, self).__init__(**properties)
+ self.identity = (type(self).__name__, ) + args
+
+
+ def __hash__(self):
+ return hash(self.identity)
+
+ def __eq__(self, other):
+
+ if not isinstance(other, ImageBase):
+ return False
+
+ return self.identity == other.identity
+
+ def __repr__(self):
+ return "<" + " ".join([repr(i) for i in self.identity]) + ">"
+
+ def load(self):
+ """
+ This function is called by the image cache code to cause this
+ image to be loaded. It's expected that children of this class
+ would override this.
+ """
+
+ assert False
+
+ def render(self, w, h, st, at):
+
+ im = cache.get(self)
+ texture = renpy.display.draw.load_texture(im)
+
+ w, h = im.get_size()
+ rv = renpy.display.render.Render(w, h)
+ rv.blit(texture, (0, 0))
+ return rv
+
+ def predict_one(self):
+ renpy.display.predict.image(self)
+
+ def predict_files(self):
+ """
+ Returns a list of files that will be accessed when this image
+ operation is performed.
+ """
+
+ return [ ]
+
+class Image(ImageBase):
+ """
+ This image manipulator loads an image from a file.
+ """
+
+ def __init__(self, filename, **properties):
+ """
+ @param filename: The filename that the image will be loaded from.
+ """
+
+ super(Image, self).__init__(filename, **properties)
+ self.filename = filename
+
+ def get_mtime(self):
+ return renpy.loader.get_mtime(self.filename)
+
+ def load(self, unscaled=False):
+
+ cache.add_load_log(self.filename)
+
+ try:
+
+ if unscaled:
+ surf = renpy.display.pgrender.load_image_unscaled(renpy.loader.load(self.filename), self.filename)
+ else:
+ surf = renpy.display.pgrender.load_image(renpy.loader.load(self.filename), self.filename)
+
+ return surf
+
+ except Exception as e:
+
+ if renpy.config.missing_image_callback:
+ im = renpy.config.missing_image_callback(self.filename)
+ if im is None:
+ raise e
+
+ return im.load()
+
+ raise
+
+ def predict_files(self):
+
+ if renpy.loader.loadable(self.filename):
+ return [ self.filename ]
+ else:
+ if renpy.config.missing_image_callback:
+ im = renpy.config.missing_image_callback(self.filename)
+ if im is not None:
+ return im.predict_files()
+
+ return [ self.filename ]
+
+class ZipFileImage(ImageBase):
+
+ def __init__(self, zipfilename, filename, mtime=0, **properties):
+ super(ZipFileImage, self).__init__(zipfilename, filename, mtime, **properties)
+
+ self.zipfilename = zipfilename
+ self.filename = filename
+
+ def load(self):
+ try:
+ zf = zipfile.ZipFile(self.zipfilename, 'r')
+ data = zf.read(self.filename)
+ sio = io.StringIO(data)
+ rv = renpy.display.pgrender.load_image(sio, self.filename)
+ zf.close()
+ return rv
+ except:
+ return renpy.display.pgrender.surface((2, 2), True)
+
+
+
+ def predict_files(self):
+ return [ ]
+
+
+
+class Composite(ImageBase):
+ """
+ :doc: im_im
+
+ This image manipulator composites multiple images together to
+ form a single image.
+
+ The `size` should be a (width, height) tuple giving the size
+ of the composed image.
+
+ The remaining positional arguments are interpreted as groups of
+ two. The first argument in a group should be an (x, y) tuple,
+ while the second should be an image manipulator. The image
+ produced by the image manipulator is composited at the location
+ given by the tuple.
+
+ ::
+
+ image girl clothed happy = im.Composite(
+ (300, 600),
+ (0, 0), "girl_body.png",
+ (0, 0), "girl_clothes.png",
+ (100, 100), "girl_happy.png"
+ )
+
+ """
+
+ def __init__(self, size, *args, **properties):
+
+ super(Composite, self).__init__(size, *args, **properties)
+
+ if len(args) % 2 != 0:
+ raise Exception("Composite requires an odd number of arguments.")
+
+ self.size = size
+ self.positions = args[0::2]
+ self.images = [ image(i) for i in args[1::2] ]
+
+ def get_mtime(self):
+ return min(i.get_mtime() for i in self.images)
+
+ def load(self):
+
+ if self.size:
+ size = self.size
+ else:
+ size = cache.get(self.images[0]).get_size()
+
+ rv = renpy.display.pgrender.surface(size, True)
+
+ for pos, im in zip(self.positions, self.images):
+ rv.blit(cache.get(im), pos)
+
+ return rv
+
+ def predict_files(self):
+
+ rv = [ ]
+
+ for i in self.images:
+ rv.extend(i.predict_files())
+
+ return rv
+
+class Scale(ImageBase):
+ """
+ :doc: im_im
+
+ An image manipulator that scales `im` (an image manipulator) to
+ `width` and `height`.
+
+ If `bilinear` is true, then bilinear interpolation is used for
+ the scaling. Otherwise, nearest neighbor interpolation is used.
+
+ ::
+
+ image logo scale = im.Scale("logo.png", 100, 150)
+ """
+
+ def __init__(self, im, width, height, bilinear=True, **properties):
+
+ im = image(im)
+ super(Scale, self).__init__(im, width, height, bilinear, **properties)
+
+ self.image = im
+ self.width = int(width)
+ self.height = int(height)
+ self.bilinear = bilinear
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ child = cache.get(self.image)
+
+ if self.bilinear:
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.scale.smoothscale(child, (self.width, self.height))
+ finally:
+ renpy.display.render.blit_lock.release()
+ else:
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.pgrender.transform_scale(child, (self.width, self.height))
+ finally:
+ renpy.display.render.blit_lock.release()
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+class FactorScale(ImageBase):
+ """
+ :doc: im_im
+
+ An image manipulator that scales `im` (a second image manipulator)
+ to `width` times its original `width`, and `height` times its
+ original height. If `height` is ommitted, it defaults to `width`.
+
+ If `bilinear` is true, then bilinear interpolation is used for
+ the scaling. Otherwise, nearest neighbor interpolation is used.
+
+ ::
+
+ image logo doubled = im.FactorScale("logo.png", 1.5)
+ """
+
+
+ def __init__(self, im, width, height=None, bilinear=True, **properties):
+
+ if height is None:
+ height = width
+
+ im = image(im)
+ super(FactorScale, self).__init__(im, width, height, bilinear, **properties)
+
+ self.image = im
+ self.width = width
+ self.height = height
+ self.bilinear = bilinear
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ surf = cache.get(self.image)
+ width, height = surf.get_size()
+
+ width = int(width * self.width)
+ height = int(height * self.height)
+
+ if self.bilinear:
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.scale.smoothscale(surf, (width, height))
+ finally:
+ renpy.display.render.blit_lock.release()
+
+ else:
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.pgrender.transform_scale(surf, (width, height))
+ finally:
+ renpy.display.render.blit_lock.release()
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+
+class Flip(ImageBase):
+ """
+ :doc: im_im
+
+ An image manipulator that flips `im` (an image manipulator)
+ vertically or horizontally. `vertical` and `horizontal` control
+ the directions in which the image is flipped.
+
+ ::
+
+ image eileen flip = im.Flip("eileen_happy.png", vertical=True)
+ """
+
+ def __init__(self, im, horizontal=False, vertical=False, **properties):
+
+ if not (horizontal or vertical):
+ raise Exception("im.Flip must be called with a true value for horizontal or vertical.")
+
+ im = image(im)
+ super(Flip, self).__init__(im, horizontal, vertical, **properties)
+
+ self.image = im
+ self.horizontal = horizontal
+ self.vertical = vertical
+
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ child = cache.get(self.image)
+
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.pgrender.flip(child, self.horizontal, self.vertical)
+ finally:
+ renpy.display.render.blit_lock.release()
+
+ return rv
+
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+
+
+class Rotozoom(ImageBase):
+ """
+ This is an image manipulator that is a smooth rotation and zoom of another image manipulator.
+ """
+
+ def __init__(self, im, angle, zoom, **properties):
+ """
+ @param im: The image to be rotozoomed.
+
+ @param angle: The number of degrees counterclockwise the image is
+ to be rotated.
+
+ @param zoom: The zoom factor. Numbers that are greater than 1.0
+ lead to the image becoming larger.
+ """
+
+ im = image(im)
+ super(Rotozoom, self).__init__(im, angle, zoom, **properties)
+
+ self.image = im
+ self.angle = angle
+ self.zoom = zoom
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ child = cache.get(self.image)
+
+ try:
+ renpy.display.render.blit_lock.acquire()
+ rv = renpy.display.pgrender.rotozoom(child, self.angle, self.zoom)
+ finally:
+ renpy.display.render.blit_lock.release()
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+
+
+class Crop(ImageBase):
+ """
+ :doc: im_im
+ :args: (im, rect)
+
+ An image manipulator that crops `rect`, a (x, y, width, height) tuple,
+ out of `im`, an image manipulator.
+
+ ::
+
+ image logo crop = im.Crop("logo.png", (0, 0, 100, 307))
+ """
+
+ def __init__(self, im, x, y=None, w=None, h=None, **properties):
+
+ im = image(im)
+
+ if y is None:
+ (x, y, w, h) = x
+
+ super(Crop, self).__init__(im, x, y, w, h, **properties)
+
+ self.image = im
+ self.x = x
+ self.y = y
+ self.w = w
+ self.h = h
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+ return cache.get(self.image).subsurface((self.x, self.y,
+ self.w, self.h))
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+
+ramp_cache = { }
+
+
+def ramp(start, end):
+ """
+ Returns a 256 character linear ramp, where the first character has
+ the value start and the last character has the value end. Such a
+ ramp can be used as a map argument of im.Map.
+ """
+
+ rv = ramp_cache.get((start, end), None)
+ if rv is None:
+
+ chars = [ ]
+
+ for i in range(0, 256):
+ i = i / 255.0
+ chars.append(chr(int( end * i + start * (1.0 - i) ) ) )
+
+ rv = "".join(chars)
+ ramp_cache[start, end] = rv
+
+ return rv
+
+identity = ramp(0, 255)
+
+class Map(ImageBase):
+ """
+ This adjusts the colors of the image that is its child. It takes
+ as arguments 4 256 character strings. If a pixel channel has a
+ value of 192, then the value of the 192nd character in the string
+ is used for the mapped pixel component.
+ """
+
+ def __init__(self, im, rmap=identity, gmap=identity, bmap=identity,
+ amap=identity, force_alpha=False, **properties):
+
+ im = image(im)
+
+ super(Map, self).__init__(im, rmap, gmap, bmap, amap, force_alpha, **properties)
+
+ self.image = im
+ self.rmap = rmap
+ self.gmap = gmap
+ self.bmap = bmap
+ self.amap = amap
+
+ self.force_alpha = force_alpha
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ surf = cache.get(self.image)
+
+ rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+ renpy.display.module.map(surf, rv,
+ self.rmap, self.gmap, self.bmap, self.amap)
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+class Twocolor(ImageBase):
+ """
+ This takes as arguments two colors, white and black. The image is
+ mapped such that pixels in white have the white color, pixels in
+ black have the black color, and shades of gray are linearly
+ interpolated inbetween. The alpha channel is mapped linearly
+ between 0 and the alpha found in the white color, the black
+ color's alpha is ignored.
+ """
+
+ def __init__(self, im, white, black, force_alpha=False, **properties):
+
+ white = renpy.easy.color(white)
+ black = renpy.easy.color(black)
+
+ im = image(im)
+
+ super(Twocolor, self).__init__(im, white, black, force_alpha, **properties)
+
+ self.image = im
+ self.white = white
+ self.black = black
+
+ self.force_alpha = force_alpha
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ surf = cache.get(self.image)
+
+ rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+ renpy.display.module.twomap(surf, rv,
+ self.white, self.black)
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+
+class Recolor(ImageBase):
+ """
+ This adjusts the colors of the image that is its child. It takes as an
+ argument 4 numbers between 0 and 255, and maps each channel of the image
+ linearly between 0 and the supplied color.
+ """
+
+ def __init__(self, im, rmul=255, gmul=255, bmul=255,
+ amul=255, force_alpha=False, **properties):
+
+ im = image(im)
+
+ super(Recolor, self).__init__(im, rmul, gmul, bmul, amul, force_alpha, **properties)
+
+ self.image = im
+ self.rmul = rmul + 1
+ self.gmul = gmul + 1
+ self.bmul = bmul + 1
+ self.amul = amul + 1
+
+ self.force_alpha = force_alpha
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ surf = cache.get(self.image)
+
+ rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+ renpy.display.module.linmap(surf, rv,
+ self.rmul, self.gmul, self.bmul, self.amul)
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+class MatrixColor(ImageBase):
+ """
+ :doc: im_matrixcolor
+
+ An image operator that uses `matrix` to linearly transform the
+ image manipulator `im`.
+
+ `Matrix` should be a list, tuple, or :func:`im.matrix` that is 20
+ or 25 elements long. If the object has 25 elements, then elements
+ past the 20th are ignored.
+
+ When the four components of the source color are R, G, B, and A,
+ which range from 0.0 to 1.0; the four components of the transformed
+ color are R', G', B', and A', with the same range; and the elements
+ of the matrix are named::
+
+ [ a, b, c, d, e,
+ f, g, h, i, j,
+ k, l, m, n, o,
+ p, q, r, s, t ]
+
+ the transformed colors can be computed with the formula::
+
+ R' = (a * R) + (b * G) + (c * B) + (d * A) + e
+ G' = (f * R) + (g * G) + (h * B) + (i * A) + j
+ B' = (k * R) + (l * G) + (m * B) + (n * A) + o
+ A' = (p * R) + (q * G) + (r * B) + (s * A) + t
+
+ The components of the transformed color are clamped to the
+ range [0.0, 1.0].
+ """
+
+ def __init__(self, im, matrix, **properties):
+
+ im = image(im)
+
+ if len(matrix) != 20 and len(matrix) != 25:
+ raise Exception("ColorMatrix expects a 20 or 25 element matrix, got %d elements." % len(matrix))
+
+ matrix = tuple(matrix)
+ super(MatrixColor, self).__init__(im, matrix, **properties)
+
+ self.image = im
+ self.matrix = matrix
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ surf = cache.get(self.image)
+
+ rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+ renpy.display.module.colormatrix(surf, rv, self.matrix)
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+class matrix(tuple):
+ """
+ :doc: im_matrixcolor
+
+ Constructs an im.matrix object from `matrix`. im.matrix objects
+ support The operations supported are matrix multiplication, scalar
+ multiplication, element-wise addition, and element-wise
+ subtraction. These operations are invoked using the standard
+ mathematical operators (\\*, \\*, +, and -, respectively). If two
+ im.matrix objects are multiplied, matrix multiplication is
+ performed, otherwise scalar multiplication is used.
+
+ `matrix` is a 20 or 25 element list or tuple. If it is 20 elements
+ long, it is padded with (0, 0, 0, 0, 1) to make a 5x5 matrix,
+ suitable for multiplication.
+ """
+
+ def __new__(cls, *args):
+
+ if len(args) == 1:
+ args = tuple(args[0])
+
+ if len(args) == 20:
+ args = args + (0, 0, 0, 0, 1)
+
+ if len(args) != 25:
+ raise Exception("Matrix expects to be given 20 or 25 entries, not %d." % len(args))
+
+ return tuple.__new__(cls, args)
+
+ def mul(self, a, b):
+
+ if not isinstance(a, matrix):
+ a = matrix(a)
+
+ if not isinstance(b, matrix):
+ b = matrix(b)
+
+ result = [ 0 ] * 25
+ for y in range(0, 5):
+ for x in range(0, 5):
+ for i in range(0, 5):
+ result[x + y * 5] += a[x + i * 5] * b[i + y * 5]
+
+ return matrix(result)
+
+ def scalar_mul(self, other):
+ other = float(other)
+ return matrix([ i * other for i in self ])
+
+ def vector_mul(self, o):
+
+ return (o[0]*self[0] + o[1]*self[1] + o[2]*self[2] + o[3]*self[3] + self[4],
+ o[0]*self[5] + o[1]*self[6] + o[2]*self[7] + o[3]*self[8] + self[9],
+ o[0]*self[10] + o[1]*self[11] + o[2]*self[12] + o[3]*self[13] + self[14],
+ o[0]*self[15] + o[1]*self[16] + o[2]*self[17] + o[3]*self[18] + self[19],
+ 1)
+
+
+ def __add__(self, other):
+ if isinstance(other, (int, float)):
+ other = float(other)
+ return matrix([ i + other for i in self ])
+
+ other = matrix(other)
+ return matrix([ i + j for i, j in zip(self, other)])
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ return self + other * -1
+
+ def __rsub__(self, other):
+ return self * -1 + other
+
+ def __mul__(self, other):
+ if isinstance(other, (int, float)):
+ return self.scalar_mul(other)
+
+ return self.mul(self, other)
+
+ def __rmul__(self, other):
+ if isinstance(other, (int, float)):
+ return self.scalar_mul(other)
+
+ return self.mul(other, self)
+
+ def __repr__(self):
+ return """\
+im.matrix(%f, %f, %f, %f, %f.
+ %f, %f, %f, %f, %f,
+ %f, %f, %f, %f, %f,
+ %f, %f, %f, %f, %f,
+ %f, %f, %f, %f, %f)""" % self
+
+
+ @staticmethod
+ def identity():
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.identity
+
+ Returns an identity matrix, one that does not change color or
+ alpha.
+ """
+
+ return matrix(1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, 1, 0)
+ @staticmethod
+ def saturation(level, desat=(0.2126, 0.7152, 0.0722)):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.saturation
+
+ Returns an im.matrix that alters the saturation of an
+ image. The alpha channel is untouched.
+
+ `level`
+ The amount of saturation in the resulting image. 1.0 is
+ the unaltered image, while 0.0 is grayscale.
+
+ `desat`
+ This is a 3-element tuple that controls how much of the
+ red, green, and blue channels will be placed into all
+ three channels of a fully desaturated image. The default
+ is based on the constants used for the luminance channel
+ of an NTSC television signal. Since the human eye is
+ mostly sensitive to green, more of the green channel is
+ kept then the other two channels.
+ """
+
+ r, g, b = desat
+
+ def I(a, b):
+ return a + (b - a) * level
+
+ return matrix(I(r, 1), I(g, 0), I(b, 0), 0, 0,
+ I(r, 0), I(g, 1), I(b, 0), 0, 0,
+ I(r, 0), I(g, 0), I(b, 1), 0, 0,
+ 0, 0, 0, 1, 0)
+
+ @staticmethod
+ def desaturate():
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.desaturate
+
+ Returns an im.matrix that desaturates the image (makes it
+ grayscale). This is equivalent to calling
+ im.matrix.saturation(0).
+ """
+
+ return matrix.saturation(0.0)
+
+ @staticmethod
+ def tint(r, g, b):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.tint
+
+ Returns an im.matrix that tints an image, without changing
+ the alpha channel. `r`, `g`, and `b` should be numbers between
+ 0 and 1, and control what fraction of the given channel is
+ placed into the final image. (For example, if `r` is .5, and
+ the value of the red channel is 100, the transformed color
+ will have a red value of 50.)
+ """
+
+ return matrix(r, 0, 0, 0, 0,
+ 0, g, 0, 0, 0,
+ 0, 0, b, 0, 0,
+ 0, 0, 0, 1, 0)
+
+ @staticmethod
+ def invert():
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.invert
+
+ Returns an im.matrix that inverts the red, green, and blue
+ channels of the image without changing the alpha channel.
+ """
+
+ return matrix(-1, 0, 0, 0, 1,
+ 0, -1, 0, 0, 1,
+ 0, 0, -1, 0, 1,
+ 0, 0, 0, 1, 0)
+
+ @staticmethod
+ def brightness(b):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.brightness
+
+ Returns an im.matrix that alters the brightness of an image.
+
+ `b`
+ The amount of change in image brightness. This should be
+ a number between -1 and 1, with -1 the darkest possible
+ image and 1 the brightest.
+ """
+
+ return matrix(1, 0, 0, 0, b,
+ 0, 1, 0, 0, b,
+ 0, 0, 1, 0, b,
+ 0, 0, 0, 1, 0)
+
+ @staticmethod
+ def opacity(o):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.opacity
+
+ Returns an im.matrix that alters the opacity of an image. An
+ `o` of 0.0 is fully transparent, while 1.0 is fully opaque.
+ """
+
+ return matrix(1, 0, 0, 0, 0,
+ 0, 1, 0, 0, 0,
+ 0, 0, 1, 0, 0,
+ 0, 0, 0, o, 0)
+
+ @staticmethod
+ def contrast(c):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.contrast
+
+ Returns an im.matrix that alters the contrast of an image. `c` should
+ be greater than 0.0, with values between 0.0 and 1.0 decreasing contrast, and
+ values greater than 1.0 increasing contrast.
+ """
+
+ return matrix.brightness(-.5) * matrix.tint(c, c, c) * matrix.brightness(.5)
+
+ # from http://www.gskinner.com/blog/archives/2005/09/flash_8_source.html
+ @staticmethod
+ def hue(h):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.hue
+
+ Returns an im.matrix that rotates the hue by `h` degrees, while
+ preserving luminosity.
+ """
+
+ h = h * math.pi / 180
+ cosVal = math.cos(h)
+ sinVal = math.sin(h)
+ lumR = 0.213
+ lumG = 0.715
+ lumB = 0.072
+ return matrix(
+ lumR+cosVal*(1-lumR)+sinVal*(-lumR),lumG+cosVal*(-lumG)+sinVal*(-lumG),lumB+cosVal*(-lumB)+sinVal*(1-lumB),0,0,
+ lumR+cosVal*(-lumR)+sinVal*(0.143),lumG+cosVal*(1-lumG)+sinVal*(0.140),lumB+cosVal*(-lumB)+sinVal*(-0.283),0,0,
+ lumR+cosVal*(-lumR)+sinVal*(-(1-lumR)),lumG+cosVal*(-lumG)+sinVal*(lumG),lumB+cosVal*(1-lumB)+sinVal*(lumB),0,0,
+ 0,0,0,1,0,
+ 0,0,0,0,1
+ )
+
+ @staticmethod
+ def colorize(black_color, white_color):
+ """
+ :doc: im_matrixcolor
+ :name: im.matrix.colorize
+
+ Returns an im.matrix that colorizes a black and white image.
+ `black_color` and `white_color` are Ren'Py style colors, so
+ they may be specfied as strings or tuples of (0-255) color
+ values. ::
+
+ # This makes black colors red, and white colors blue.
+ image logo colored = im.MatrixColor(
+ "bwlogo.png",
+ im.matrix.colorize("#f00", "#00f"))
+
+ """
+
+ (r0, g0, b0, _a0) = renpy.easy.color(black_color)
+ (r1, g1, b1, _a1) = renpy.easy.color(white_color)
+
+ r0 /= 255.0
+ g0 /= 255.0
+ b0 /= 255.0
+ r1 /= 255.0
+ g1 /= 255.0
+ b1 /= 255.0
+
+ return matrix((r1-r0), 0, 0, 0, r0,
+ 0, (g1-g0), 0, 0, g0,
+ 0, 0, (b1-b0), 0, b0,
+ 0, 0, 0, 1, 0)
+
+
+
+def Grayscale(im, desat=(0.2126, 0.7152, 0.0722), **properties):
+ """
+ :doc: im_im
+ :args: (im, **properties)
+
+ An image manipulator that creats a desaturated version of the image
+ manipulator `im`.
+ """
+
+ return MatrixColor(im, matrix.saturation(0.0, desat), **properties)
+
+
+def Sepia(im, tint=(1.0, .94, .76), desat=(0.2126, 0.7152, 0.0722), **properties):
+ """
+ :doc: im_im
+ :args: (im, **properties)
+
+ An image manipulator that creates a sepia-toned version of the image
+ manipulator `im`.
+ """
+
+ return MatrixColor(im, matrix.saturation(0.0, desat) * matrix.tint(tint[0], tint[1], tint[2]), **properties)
+
+
+def Color(im, color):
+ """
+ This recolors the supplied image, mapping colors such that black is
+ black and white is the supplied color.
+ """
+
+ r, g, b, a = renpy.easy.color(color)
+
+ return Recolor(im, r, g, b, a)
+
+
+def Alpha(image, alpha, **properties):
+ """
+ Returns an alpha-mapped version of the image. Alpha is the maximum
+ alpha that this image can have, a number between 0.0 (fully
+ transparent) and 1.0 (opaque).
+
+ If an image already has an alpha channel, values in that alpha
+ channel are reduced as appropriate.
+ """
+
+ return Recolor(image, 255, 255, 255, int(255 * alpha), force_alpha=True, **properties)
+
+class Tile(ImageBase):
+ """
+ :doc: im_im
+
+ An image manipulator that tiles the image manipulator `im`, until
+ it is `size`.
+
+ `size`
+ If not None, a (width, height) tuple. If None, this defaults to
+ (:var:`config.screen_width`, :var:`config.screen_height`).
+ """
+
+ def __init__(self, im, size=None, **properties):
+
+ im = image(im)
+
+ super(Tile, self).__init__(im, size, **properties)
+ self.image = im
+ self.size = size
+
+ def get_mtime(self):
+ return self.image.get_mtime()
+
+ def load(self):
+
+ size = self.size
+
+ if size is None:
+ size = (renpy.config.screen_width, renpy.config.screen_height)
+
+ surf = cache.get(self.image)
+
+ rv = renpy.display.pgrender.surface(size, True)
+
+ width, height = size
+ sw, sh = surf.get_size()
+
+ for y in range(0, height, sh):
+ for x in range(0, width, sw):
+ rv.blit(surf, (x, y))
+
+ return rv
+
+ def predict_files(self):
+ return self.image.predict_files()
+
+class AlphaMask(ImageBase):
+ """
+ :doc: im_im
+
+ An image manipulator that takes two image manipulators, `base` and
+ `mask`, as arguments. It replaces the alpha channel of `base` with
+ the red channel of `mask`.
+
+ This is used to provide an image's alpha channel in a second
+ image, like having one jpeg for color data, and a second one
+ for alpha. In some cases, two jpegs can be smaller than a
+ single png file.
+ """
+
+ def __init__(self, base, mask, **properties):
+ super(AlphaMask, self).__init__(base, mask, **properties)
+
+ self.base = image(base)
+ self.mask = image(mask)
+
+ def get_mtime(self):
+ return max(self.base.get_mtime(), self.image.get_mtime())
+
+ def load(self):
+
+ basesurf = cache.get(self.base)
+ masksurf = cache.get(self.mask)
+
+ if basesurf.get_size() != masksurf.get_size():
+ raise Exception("AlphaMask surfaces must be the same size.")
+
+ # Used to copy the surface.
+ rv = renpy.display.pgrender.copy_surface(basesurf)
+ renpy.display.module.alpha_munge(masksurf, rv, identity)
+
+ return rv
+
+ def predict_files(self):
+ return self.base.predict_files() + self.mask.predict_files()
+
+def image(arg, loose=False, **properties):
+ """
+ :doc: im_image
+ :name: Image
+ :args: (filename, **properties)
+
+ Loads an image from a file. `filename` is a
+ string giving the name of the file.
+
+ `filename` should be a JPEG or PNG file with an appropriate
+ extension.
+ """
+
+ """
+ (Actually, the user documentation is a bit misleading, as
+ this tries for compatibility with several older forms of
+ image specification.)
+
+ If the loose argument is False, then this will report an error if an
+ arbitrary argument is given. If it's True, then the argument is passed
+ through unchanged.
+ """
+
+ if isinstance(arg, ImageBase):
+ return arg
+
+ elif isinstance(arg, str):
+ return Image(arg, **properties)
+
+ elif isinstance(arg, renpy.display.image.ImageReference):
+ arg.find_target()
+ return image(arg.target, loose=loose, **properties)
+
+ elif isinstance(arg, tuple):
+ params = [ ]
+
+ for i in arg:
+ params.append((0, 0))
+ params.append(i)
+
+ return Composite(None, *params)
+
+ elif loose:
+ return arg
+
+ if isinstance(arg, renpy.display.core.Displayable):
+ raise Exception("Expected an image, but got a general displayable.")
+ else:
+ raise Exception("Could not construct image from argument.")
+
+
+def load_image(fn):
+ """
+ This loads an image from the given filename, using the cache.
+ """
+
+ surf = cache.get(image(fn))
+ return renpy.display.draw.load_texture(surf)
diff --git a/unrpyc/renpy/display/image.py b/unrpyc/renpy/display/image.py
new file mode 100644
index 0000000..362f87a
--- /dev/null
+++ b/unrpyc/renpy/display/image.py
@@ -0,0 +1,397 @@
+# 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 some miscellaneous displayables that involve images.
+# Most of the guts of this file have been moved into im.py, with only some
+# of the stuff thar uses images remaining.
+
+import renpy.display
+import renpy.text
+from renpy.display.render import render, Render
+
+import collections
+
+# A map from image name to the displayable object corresponding to that
+# image.
+images = { }
+
+# A map from image tag to lists of possible attributes for images with that
+# tag.
+image_attributes = collections.defaultdict(list)
+
+def register_image(name, d):
+ """
+ Registers the existence of an image with `name`, and that the image
+ used displayable d.
+ """
+
+ tag = name[0]
+ rest = name[1:]
+
+ images[name] = d
+ image_attributes[tag].append(rest)
+
+
+def wrap_render(child, w, h, st, at):
+ rend = render(child, w, h, st, at)
+ rv = Render(rend.width, rend.height)
+ rv.blit(rend, (0, 0))
+ return rv
+
+class ImageReference(renpy.display.core.Displayable):
+ """
+ ImageReference objects are used to reference images by their name,
+ which is a tuple of strings corresponding to the name used to define
+ the image in an image statment.
+ """
+
+ nosave = [ 'target' ]
+ target = None
+ param_target = None
+
+ def __init__(self, name, **properties):
+ """
+ @param name: A tuple of strings, the name of the image. Or else
+ a displayable, containing the image directly.
+ """
+
+ super(ImageReference, self).__init__(**properties)
+
+ self.name = name
+
+ def _get_parameterized(self):
+ if self.param_target:
+ return self.param_target._get_parameterized()
+
+ return self
+
+ def find_target(self):
+
+ if self.param_target:
+ self.target = self.param_target
+ return None
+
+ name = self.name
+
+ if isinstance(name, renpy.display.core.Displayable):
+ self.target = name
+ return True
+
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ parameters = [ ]
+
+ def error(msg):
+ self.target = renpy.text.text.Text(msg, color=(255, 0, 0, 255), xanchor=0, xpos=0, yanchor=0, ypos=0)
+
+ if renpy.config.debug:
+ raise Exception(msg)
+
+
+ # Scan through, searching for an image (defined with an
+ # input statement) that is a prefix of the given name.
+ while name:
+ if name in images:
+ target = images[name]
+
+ try:
+ self.target = target.parameterize(name, parameters)
+ if self.target is not target:
+ self.param_target = self.target
+
+ except Exception as e:
+ if renpy.config.debug:
+ raise
+
+ error(str(e))
+
+ return True
+
+ else:
+ parameters.insert(0, name[-1])
+ name = name[:-1]
+
+ error("Image '%s' not found." % ' '.join(self.name))
+ return False
+
+ def parameterize(self, name, parameters):
+ if not self.target:
+ self.find_target()
+
+ return self.target.parameterize(name, parameters)
+
+ def _hide(self, st, at, kind):
+ if not self.target:
+ self.find_target()
+
+ return self.target._hide(st, at, kind)
+
+ def set_transform_event(self, event):
+ if not self.target:
+ self.find_target()
+
+ return self.target.set_transform_event(event)
+
+ def event(self, ev, x, y, st):
+ if not self.target:
+ self.find_target()
+
+ return self.target.event(ev, x, y, st)
+
+ def render(self, width, height, st, at):
+ if not self.target:
+ self.find_target()
+
+ return wrap_render(self.target, width, height, st, at)
+
+ def get_placement(self):
+ if not self.target:
+ self.find_target()
+
+ if not renpy.config.imagereference_respects_position:
+ return self.target.get_placement()
+
+ xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.target.get_placement()
+
+ if xpos is None:
+ xpos = self.style.xpos
+
+ if ypos is None:
+ ypos = self.style.ypos
+
+ if xanchor is None:
+ xanchor = self.style.xanchor
+
+ if yanchor is None:
+ yanchor = self.style.yanchor
+
+ return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+ def visit(self):
+ if not self.target:
+ self.find_target()
+
+ return [ self.target ]
+
+
+
+class ShownImageInfo(renpy.object.Object):
+ """
+ This class keeps track of which images are being shown right now,
+ and what the attributes of those images are. (It's used for a similar
+ purpose during prediction, regarding the state in the future.)
+ """
+
+ __version__ = 2
+
+ def __init__(self, old=None):
+ """
+ Creates a new object. If `old` is given, copies the default state
+ from old, otherwise initializes the object to a default state.
+ """
+
+ if old is None:
+
+ # A map from (layer, tag) -> tuple of attributes
+ # This doesn't necessarily correspond to something that is
+ # currently showing, as we can remember the state of a tag
+ # for use in SideImage.
+ self.attributes = { }
+
+ # A set of (layer, tag) pairs that are being shown on the
+ # screen right now.
+ self.shown = set()
+
+ else:
+ self.attributes = old.attributes.copy()
+ self.shown = old.shown.copy()
+
+
+ def after_upgrade(self, version):
+ if version < 2:
+
+ self.attributes = { }
+ self.shown = set()
+
+ for layer in self.images:
+ for tag in self.images[layer]:
+ self.attributes[layer, tag] = self.images[layer][tag][1:]
+ self.shown.add((layer, tag))
+
+ def get_attributes(self, layer, tag):
+ """
+ Get the attributes associated the image with tag on the given
+ layer.
+ """
+
+ return self.attributes.get((layer, tag), ())
+
+ def showing(self, layer, name):
+ """
+ Returns true if name is the prefix of an image that is showing
+ on layer, or false otherwise.
+ """
+
+ tag = name[0]
+ rest = name[1:]
+
+ if (layer, tag) not in self.shown:
+ return None
+
+ shown = self.attributes[layer, tag]
+
+ if len(shown) < len(rest):
+ return False
+
+ for a, b in zip(shown, rest):
+ if a != b:
+ return False
+
+ return True
+
+ def predict_scene(self, layer):
+ """
+ Predicts the scene statement being called on layer.
+ """
+
+ for l, t in list(self.attributes.keys()):
+ if l == layer:
+ del self.attributes[l, t]
+
+ self.shown = set((l, t) for l, t in self.shown if l != layer)
+
+ def predict_show(self, layer, name, show=True):
+ """
+ Predicts name being shown on layer.
+
+ `show`
+ If True, the image will be flagged as being shown to the user. If
+ False, only the attributes will be updated.
+ """
+
+ tag = name[0]
+ rest = name[1:]
+
+ self.attributes[layer, tag] = rest
+
+ if show:
+ self.shown.add((layer, tag))
+
+ def predict_hide(self, layer, name):
+ tag = name[0]
+
+ if (layer, tag) in self.attributes:
+ del self.attributes[layer, tag]
+
+ self.shown.discard((layer, tag))
+
+
+ def apply_attributes(self, layer, tag, name):
+ """
+ Given a layer, tag, and an image name (with attributes),
+ returns the canonical name of an image, if one exists. Raises
+ an exception if it's ambiguious, and returns None if an image
+ with that name couldn't be found.
+ """
+
+ # If the name matches one that exactly exists, return it.
+ if name in images:
+ return name
+
+ nametag = name[0]
+
+ # The set of attributes a matching image must have.
+ required = set(name[1:])
+
+ # The set of attributes a matching image may have.
+ optional = set(self.attributes.get((layer, tag), [ ]))
+
+ # Deal with banned attributes..
+ for i in name[1:]:
+ if i[0] == "-":
+ optional.discard(i[1:])
+ required.discard(i)
+
+ return self.choose_image(nametag, required, optional, name)
+
+ def choose_image(self, tag, required, optional, exception_name):
+ """
+ """
+
+ # The longest length of an image that matches.
+ max_len = 0
+
+ # The list of matching images.
+ matches = None
+
+ for attrs in image_attributes[tag]:
+
+ num_required = 0
+
+ for i in attrs:
+ if i in required:
+ num_required += 1
+ continue
+
+ elif i not in optional:
+ break
+
+ else:
+
+ # We don't have any not-found attributes. But we might not
+ # have all of the attributes.
+
+ if num_required != len(required):
+ continue
+
+ len_attrs = len(attrs)
+
+ if len_attrs < max_len:
+ continue
+
+ if len_attrs > max_len:
+ max_len = len_attrs
+ matches = [ ]
+
+ matches.append((tag, ) + attrs)
+
+ if matches is None:
+ return None
+
+ if len(matches) == 1:
+ return matches[0]
+
+ if exception_name:
+ raise Exception("Showing '" + " ".join(exception_name) + "' is ambiguous, possible images include: " + ", ".join(" ".join(i) for i in matches))
+ else:
+ return None
+
+renpy.display.core.ImagePredictInfo = ShownImageInfo
+
+
+# Functions that have moved from this module to other modules,
+# that live here for the purpose of backward-compatibility.
+Image = renpy.display.im.image
+Solid = renpy.display.imagelike.Solid
+Frame = renpy.display.imagelike.Frame
+ImageButton = renpy.display.behavior.ImageButton
+
diff --git a/unrpyc/renpy/display/imagelike.py b/unrpyc/renpy/display/imagelike.py
new file mode 100644
index 0000000..aea1d15
--- /dev/null
+++ b/unrpyc/renpy/display/imagelike.py
@@ -0,0 +1,382 @@
+# 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.
+
+import renpy.display
+from renpy.display.render import render, Render, Matrix2D
+
+# This file contains displayables that are image-like, because they take
+# up a rectangular area of the screen, and do not respond to input.
+
+class Solid(renpy.display.core.Displayable):
+ """
+ :doc: disp_imagelike
+
+ A displayable that fills the area its assigned with `color`.
+
+ ::
+
+ image white = Solid("#fff")
+
+ """
+
+ def __init__(self, color, **properties):
+
+ super(Solid, self).__init__(**properties)
+
+ if color is not None:
+ self.color = renpy.easy.color(color)
+ else:
+ self.color = None
+
+ def visit(self):
+ return [ ]
+
+ def render(self, width, height, st, at):
+
+ color = self.color or self.style.color
+
+ rv = Render(width, height)
+
+ if color is None or width <= 0 or height <= 0:
+ return rv
+
+ SIZE = 10
+
+ if width < SIZE or height < SIZE:
+ tex = renpy.display.draw.solid_texture(width, height, color)
+ else:
+ tex = renpy.display.draw.solid_texture(SIZE, SIZE, color)
+ rv.forward = Matrix2D(1.0 * SIZE / width, 0, 0, 1.0 * SIZE / height)
+ rv.reverse = Matrix2D(1.0 * width / SIZE, 0, 0, 1.0 * height / SIZE)
+
+ rv.blit(tex, (0, 0))
+
+ return rv
+
+class Frame(renpy.display.core.Displayable):
+ """
+ :doc: disp_imagelike
+ :args: (image, xborder, yborder, tile=False, **properties)
+
+ A displayable that resizes an image to fill the available area,
+ while preserving the width and height of its borders. is often
+ used as the background of a window or button.
+
+ .. figure:: frame_example.png
+
+ Using a frame to resize an image to double its size.
+
+ `image`
+ An image manipulator that will be resized by this frame.
+
+ `left`
+ The size of the border on the left side.
+
+ `top`
+ The size of the border on the top.
+
+ `right`
+ The size of the border on the right side. If None, defaults
+ to `left`.
+
+ `bottom`
+ The side of the border on the bottom. If None, defaults to `top`.
+
+ `tile`
+ If true, tiling is used to resize sections of the image,
+ rather than scaling.
+
+ ::
+
+ # Resize the background of the text window if it's too small.
+ init python:
+ style.window.background = Frame("frame.png", 10, 10)
+ """
+
+ __version__ = 1
+
+ def after_upgrade(self, version):
+ if version < 2:
+ self.left = self.xborder
+ self.right = self.xborder
+ self.top = self.yborder
+ self.bottom = self.yborder
+
+ def __init__(self, image, left, top, right=None, bottom=None, bilinear=True, tile=False, **properties):
+ super(Frame, self).__init__(**properties)
+
+ self.image = renpy.easy.displayable(image)
+ self.tile = tile
+
+ if right is None:
+ right = left
+ if bottom is None:
+ bottom = top
+
+ self.left = left
+ self.top = top
+ self.right = right
+ self.bottom = bottom
+
+ def render(self, width, height, st, at):
+
+ crend = render(self.image, width, height, st, at)
+
+ sw, sh = crend.get_size()
+ sw = int(sw)
+ sh = int(sh)
+
+ dw = int(width)
+ dh = int(height)
+
+ bw = self.left + self.right
+ bh = self.top + self.bottom
+
+ xborder = min(bw, sw - 2, dw)
+ if xborder:
+ left = self.left * xborder / bw
+ right = self.right * xborder / bw
+ else:
+ left = 0
+ right = 0
+
+ yborder = min(bh, sh - 2, dh)
+ if yborder:
+ top = self.top * yborder / bh
+ bottom = self.bottom * yborder / bh
+ else:
+ top = 0
+ bottom = 0
+
+ if renpy.display.draw.info["renderer"] == "sw":
+ return self.sw_render(crend, dw, dh, left, top, right, bottom)
+
+ def draw(x0, x1, y0, y1):
+
+ # Compute the coordinates of the left, right, top, and
+ # bottom sides of the region, for both the source and
+ # destination surfaces.
+
+ # left side.
+ if x0 >= 0:
+ dx0 = x0
+ sx0 = x0
+ else:
+ dx0 = dw + x0
+ sx0 = sw + x0
+
+ # right side.
+ if x1 > 0:
+ dx1 = x1
+ sx1 = x1
+ else:
+ dx1 = dw + x1
+ sx1 = sw + x1
+
+ # top side.
+ if y0 >= 0:
+ dy0 = y0
+ sy0 = y0
+ else:
+ dy0 = dh + y0
+ sy0 = sh + y0
+
+ # bottom side
+ if y1 > 0:
+ dy1 = y1
+ sy1 = y1
+ else:
+ dy1 = dh + y1
+ sy1 = sh + y1
+
+ # Quick exit.
+ if sx0 == sx1 or sy0 == sy1:
+ return
+
+ # Compute sizes.
+ csw = sx1 - sx0
+ csh = sy1 - sy0
+ cdw = dx1 - dx0
+ cdh = dy1 - dy0
+
+ if csw <= 0 or csh <= 0 or cdh <= 0 or cdw <= 0:
+ return
+
+ # Get a subsurface.
+ cr = crend.subsurface((sx0, sy0, csw, csh))
+
+ # Scale or tile if we have to.
+ if csw != cdw or csh != cdh:
+
+ if self.tile:
+ newcr = Render(cdw, cdh)
+ newcr.clipping = True
+
+ for x in range(0, cdw, csw):
+ for y in range(0, cdh, csh):
+ newcr.blit(cr, (x, y))
+
+ cr = newcr
+
+ else:
+
+ newcr = Render(cdw, cdh)
+ newcr.forward = Matrix2D(1.0 * csw / cdw, 0, 0, 1.0 * csh / cdh)
+ newcr.reverse = Matrix2D(1.0 * cdw / csw, 0, 0, 1.0 * cdh / csh)
+ newcr.blit(cr, (0, 0))
+
+ cr = newcr
+
+ # Blit.
+ rv.blit(cr, (dx0, dy0))
+ return
+
+ rv = Render(dw, dh)
+
+ self.draw_pattern(draw, left, top, right, bottom)
+
+ return rv
+
+ def draw_pattern(self, draw, left, top, right, bottom):
+ # Top row.
+ if top:
+
+ if left:
+ draw(0, left, 0, top)
+
+ draw(left, -right, 0, top)
+
+ if right:
+ draw(-right, 0, 0, top)
+
+ # Middle row.
+ if left:
+ draw(0, left, top, -bottom)
+
+ draw(left, -right, top, -bottom)
+
+ if right:
+ draw(-right, 0, top, -bottom)
+
+ # Bottom row.
+ if bottom:
+ if left:
+ draw(0, left, -bottom, 0)
+
+ draw(left, -right, -bottom, 0)
+
+ if right:
+ draw(-right, 0, -bottom, 0)
+
+
+
+ def sw_render(self, crend, dw, dh, left, top, right, bottom):
+
+ source = crend.render_to_texture(True)
+ sw, sh = source.get_size()
+
+ dest = renpy.display.swdraw.surface(dw, dh, True)
+ rv = dest
+
+ def draw(x0, x1, y0, y1):
+
+ # Compute the coordinates of the left, right, top, and
+ # bottom sides of the region, for both the source and
+ # destination surfaces.
+
+ # left side.
+ if x0 >= 0:
+ dx0 = x0
+ sx0 = x0
+ else:
+ dx0 = dw + x0
+ sx0 = sw + x0
+
+ # right side.
+ if x1 > 0:
+ dx1 = x1
+ sx1 = x1
+ else:
+ dx1 = dw + x1
+ sx1 = sw + x1
+
+ # top side.
+ if y0 >= 0:
+ dy0 = y0
+ sy0 = y0
+ else:
+ dy0 = dh + y0
+ sy0 = sh + y0
+
+ # bottom side
+ if y1 > 0:
+ dy1 = y1
+ sy1 = y1
+ else:
+ dy1 = dh + y1
+
+ sy1 = sh + y1
+
+ # Quick exit.
+ if sx0 == sx1 or sy0 == sy1 or dx1 <= dx0 or dy1 <= dy0:
+ return
+
+ # Compute sizes.
+ srcsize = (sx1 - sx0, sy1 - sy0)
+ dstsize = (int(dx1 - dx0), int(dy1 - dy0))
+
+ # Get a subsurface.
+ surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
+
+ # Scale or tile if we have to.
+ if dstsize != srcsize:
+ if self.tile:
+ tilew, tileh = srcsize
+ dstw, dsth = dstsize
+
+ surf2 = renpy.display.pgrender.surface_unscaled(dstsize, surf)
+
+ for y in range(0, dsth, tileh):
+ for x in range(0, dstw, tilew):
+ surf2.blit(surf, (x, y))
+
+ surf = surf2
+
+ else:
+ surf2 = renpy.display.scale.real_transform_scale(surf, dstsize)
+ surf = surf2
+
+ # Blit.
+ dest.blit(surf, (dx0, dy0))
+
+ self.draw_pattern(draw, left, top, right, bottom)
+
+ rrv = renpy.display.render.Render(dw, dh)
+ rrv.blit(rv, (0, 0))
+ rrv.depends_on(crend)
+
+ # And, finish up.
+ return rrv
+
+ def visit(self):
+ return [ self.image ]
+
+
diff --git a/unrpyc/renpy/display/imagemap.py b/unrpyc/renpy/display/imagemap.py
new file mode 100644
index 0000000..380bf80
--- /dev/null
+++ b/unrpyc/renpy/display/imagemap.py
@@ -0,0 +1,233 @@
+# 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 handles imagemap caching.
+
+import pygame
+import renpy.display
+
+from renpy.display.render import render
+
+import hashlib
+import os
+
+# A list of cache images we've already written.
+cached = set()
+
+class ImageMapCrop(renpy.display.core.Displayable):
+ """
+ This handles the cropping of uncached imagemap components.
+ """
+
+ def __init__(self, child, rect):
+ super(ImageMapCrop, self).__init__()
+
+ self.child = child
+ self.rect = rect
+
+ def visit(self):
+ return [ self.child ]
+
+ def render(self, width, height, st, at):
+ cr = render(self.child, width, height, st, at)
+ return cr.subsurface(self.rect)
+
+
+class ImageCacheCrop(renpy.display.core.Displayable):
+ """
+ This handles the cropping of an imagemap component.
+ """
+
+ def __init__(self, cache, index):
+ super(ImageCacheCrop, self).__init__()
+
+ # The cache object we're associated with.
+ self.cache = cache
+
+ # The index of
+ self.index = index
+
+ def visit(self):
+ return self.cache.visit(self.index)
+
+ def render(self, width, height, st, at):
+ return self.cache.render(self.index, width, height, st, at)
+
+class ImageMapCache(renpy.object.Object):
+
+ def __init__(self, enable):
+ self.md5 = hashlib.md5()
+
+ # A list of (image, rect) tuples. The index in this list is used
+ # as a unique identifier for an ImageCacheCrop object.
+ self.imagerect = [ ]
+
+ # A map from (image, rect) to ImageCacheCrop object.
+ self.hotspots = { }
+
+ # A list of (width, height, index) tuples.
+ self.areas = [ ]
+
+ # The image containing our children.
+ self.cache = None
+
+ # A list that, for each hotspot, gives the rectangle in the cache
+ # image corresponding to that hotspot.
+ self.cache_rect = None
+
+ # The size of the cache.
+ self.cache_width = None
+ self.cache_height = None
+
+ self.enable = enable
+
+ def visit(self, index):
+ if self.cache is not None:
+ return [ self.cache ]
+ else:
+ return [ self.imagerect[index][0] ]
+
+ def crop(self, d, rect):
+ if not isinstance(d, renpy.display.im.ImageBase) or \
+ not renpy.config.imagemap_cache or \
+ not self.enable:
+ return ImageMapCrop(d, rect)
+
+ key = (d, rect)
+ rv = self.hotspots.get(key, None)
+ if rv is not None:
+ return rv
+
+ self.md5.update(repr(d.identity))
+ self.md5.update(repr(d.identity))
+
+ index = len(self.imagerect)
+ rv = ImageCacheCrop(self, index)
+
+ self.imagerect.append(key)
+ self.hotspots[key] = rv
+ self.areas.append((rect[2] + 2, rect[3] + 2, index))
+
+ return rv
+
+ def layout(self):
+ self.areas.sort()
+ self.areas.reverse()
+ self.cache_rect = [ None ] * len(self.areas)
+
+ # The width of the cache image.
+ width = self.areas[0][0]
+
+ x = 0
+ y = 0
+ line_height = 0
+
+ for w, h, i in self.areas:
+
+ if x + w > width:
+ y += line_height
+ line_height = 0
+ x = 0
+
+ self.cache_rect[i] = (x+1, y+1, w-2, h-2)
+
+ x += w
+ if line_height < h:
+ line_height = h
+
+ self.cache_width = width
+ self.cache_height = y + line_height
+
+ def write_cache(self, filename):
+
+ if filename in cached:
+ return
+
+ cached.add(filename)
+
+ # If all of our dependencies are of the same age or less,
+ # we don't need to rebuild the cache.
+
+ if renpy.loader.loadable(filename):
+ d_set = set()
+ mtime = 0
+
+ for i, rect in self.imagerect:
+ if i in d_set:
+ continue
+
+ d_set.add(i)
+ mtime = max(i.get_mtime(), mtime)
+
+ if renpy.loader.get_mtime(filename) >= mtime:
+ return
+
+ fn = os.path.join(renpy.config.gamedir, filename)
+ dir = os.path.dirname(fn) #@ReservedAssignment
+
+ if not os.path.exists(dir):
+ os.makedirs(dir)
+
+ cache = pygame.Surface((self.cache_width, self.cache_height), pygame.SRCALPHA, 32)
+
+ for i, (d, rect) in enumerate(self.imagerect):
+ x, y, _w, _h = self.cache_rect[i]
+
+ surf = renpy.display.im.cache.get(d).subsurface(rect)
+ cache.blit(surf, (x, y))
+
+ pygame.image.save(cache, renpy.exports.fsencode(fn))
+
+ def finish(self):
+ if not self.areas:
+ return
+
+ filename = "im-%s.png" % (self.md5.hexdigest())
+
+ if renpy.game.preferences.language:
+ filename = renpy.game.preferences.language + "-" + filename
+
+ filename = "cache/" + filename
+
+ self.md5 = None
+
+ self.layout()
+
+ if renpy.config.developer:
+ try:
+ self.write_cache(filename)
+ except:
+ pass
+
+ if renpy.loader.loadable(filename):
+ self.cache = renpy.display.im.Image(filename)
+
+
+ def render(self, index, width, height, st, at):
+ if self.cache is None:
+ d, rect = self.imagerect[index]
+ return render(d, width, height, st, at).subsurface(rect)
+
+ return render(self.cache, width, height, st, at).subsurface(self.cache_rect[index])
+
+
+
+
diff --git a/unrpyc/renpy/display/joystick.py b/unrpyc/renpy/display/joystick.py
new file mode 100644
index 0000000..c606dd9
--- /dev/null
+++ b/unrpyc/renpy/display/joystick.py
@@ -0,0 +1,126 @@
+# 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 is responsible for joystick support in Ren'Py.
+
+import os
+import pygame
+
+import renpy.display
+
+# Do we have a joystick enabled?
+enabled = False
+
+# The old states for each axis.
+old_axis_states = { }
+
+def init():
+ """
+ Initialize the joystick system.
+ """
+
+ global enabled
+
+ if not renpy.config.joystick:
+ return
+
+ if 'RENPY_DISABLE_JOYSTICK' in os.environ:
+ return
+
+ try:
+ pygame.joystick.init()
+
+ for i in range(0, pygame.joystick.get_count()):
+ pygame.joystick.Joystick(i).init()
+ enabled = True
+ except:
+ if renpy.config.debug:
+ raise
+
+def event(ev):
+
+ if not enabled:
+ return ev
+
+ if ev.type == pygame.JOYAXISMOTION:
+
+ if not renpy.display.interface.focused:
+ return None
+
+ if ev.value >= 0.5:
+ state = "Positive"
+ elif ev.value <= -0.5:
+ state = "Negative"
+ else:
+ state = None
+
+ oldstate = old_axis_states.get((ev.joy, ev.axis), None)
+
+ if state == oldstate:
+ return None
+
+ if oldstate:
+ release = "Axis %d.%d %s" % (ev.joy, ev.axis, oldstate)
+ else:
+ release = None
+
+ old_axis_states[ev.joy, ev.axis] = state
+
+ if state:
+ press = "Axis %d.%d %s" % (ev.joy, ev.axis, state)
+ else:
+ press = None
+
+ if not press and not release:
+ return None
+
+ return pygame.event.Event(renpy.display.core.JOYEVENT,
+ press=press, release=release)
+
+ if ev.type == pygame.JOYBUTTONDOWN:
+
+ if not renpy.display.interface.focused:
+ return None
+
+ return pygame.event.Event(renpy.display.core.JOYEVENT,
+ press="Button %d.%d" % (ev.joy, ev.button),
+ release=None)
+ if ev.type == pygame.JOYBUTTONUP:
+
+ if not renpy.display.interface.focused:
+ return None
+
+ return pygame.event.Event(renpy.display.core.JOYEVENT,
+ press=None,
+ release="Button %d.%d" % (ev.joy, ev.button))
+
+ return ev
+
+class JoyBehavior(renpy.display.layout.Null):
+ """
+ This is a behavior intended for joystick calibration. If a joystick
+ event occurs, this returns it as a string.
+ """
+
+ def event(self, ev, x, y, st):
+ if ev.type == renpy.display.core.JOYEVENT:
+ return ev.press
+
diff --git a/unrpyc/renpy/display/layout.py b/unrpyc/renpy/display/layout.py
new file mode 100644
index 0000000..e07e28d
--- /dev/null
+++ b/unrpyc/renpy/display/layout.py
@@ -0,0 +1,1744 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains classes that handle layout of displayables on
+# the screen.
+
+from renpy.display.render import render, Render
+import renpy.display
+
+
+def scale(num, base):
+ """
+ If num is a float, multiplies it by base and returns that. Otherwise,
+ returns num unchanged.
+ """
+
+ if isinstance(num, float):
+ return num * base
+ else:
+ return num
+
+class Null(renpy.display.core.Displayable):
+ """
+ :doc: disp_imagelike
+
+ A displayable that creates an empty box on the screen. The size
+ of the box is controlled by `width` and `height`. This can be used
+ when a displayable requires a child, but no child is suitable, or
+ as a spacer inside a box.
+
+ ::
+
+ image logo spaced = HBox("logo.png", Null(width=100), "logo.png")
+
+ """
+
+ def __init__(self, width=0, height=0, **properties):
+ super(Null, self).__init__(**properties)
+ self.width = width
+ self.height = height
+
+ def render(self, width, height, st, at):
+ rv = renpy.display.render.Render(self.width, self.height)
+
+ if self.focusable:
+ rv.add_focus(self, None, None, None, None, None)
+
+ return rv
+
+
+class Container(renpy.display.core.Displayable):
+ """
+ This is the base class for containers that can have one or more
+ children.
+
+ @ivar children: A list giving the children that have been added to
+ this container, in the order that they were added in.
+
+ @ivar child: The last child added to this container. This is also
+ used to access the sole child in containers that can only hold
+ one child.
+
+ @ivar offsets: A list giving offsets for each of our children.
+ It's expected that render will set this up each time it is called.
+
+ @ivar sizes: A list giving sizes for each of our children. It's
+ also expected that render will set this each time it is called.
+
+ """
+
+ # We indirect all list creation through this, so that we can
+ # use RevertableLists if we want.
+ _list_type = list
+
+ def __init__(self, *args, **properties):
+
+ self.children = self._list_type()
+ self.child = None
+ self.offsets = self._list_type()
+
+ for i in args:
+ self.add(i)
+
+ super(Container, self).__init__(**properties)
+
+ def set_style_prefix(self, prefix, root):
+ super(Container, self).set_style_prefix(prefix, root)
+
+ for i in self.children:
+ i.set_style_prefix(prefix, False)
+
+ def add(self, d):
+ """
+ Adds a child to this container.
+ """
+
+ child = renpy.easy.displayable(d)
+
+ self.children.append(child)
+
+ self.child = child
+ self.offsets = self._list_type()
+
+ def remove(self, d):
+ """
+ Removes the first instance of child from this container. May
+ not work with all containers.
+ """
+
+ for i, c in enumerate(self.children):
+ if c is d:
+ break
+ else:
+ return
+
+ self.children.pop(i) # W0631
+ self.offsets = self._list_type()
+
+ if self.children:
+ self.child = self.children[-1]
+ else:
+ self.child = None
+
+
+ def update(self):
+ """
+ This should be called if a child is added to this
+ displayable outside of the render function.
+ """
+
+ renpy.display.render.invalidate(self)
+
+
+ def render(self, width, height, st, at):
+
+ rv = Render(width, height)
+ self.offsets = self._list_type()
+
+ for c in self.children:
+ cr = render(c, width, height, st, at)
+ offset = c.place(rv, 0, 0, width, height, cr)
+ self.offsets.append(offset)
+
+ return rv
+
+
+ def event(self, ev, x, y, st):
+
+ children = self.children
+ offsets = self.offsets
+
+ for i in range(len(offsets) - 1, -1, -1):
+
+ d = children[i]
+ xo, yo = offsets[i]
+
+ rv = d.event(ev, x - xo, y - yo, st)
+ if rv is not None:
+ return rv
+
+ return None
+
+ def visit(self):
+ return self.children
+
+ # These interact with the ui functions to allow use as a context
+ # manager.
+
+ def __enter__(self):
+
+ renpy.ui.context_enter(self)
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+
+ renpy.ui.context_exit(self)
+ return False
+
+
+
+
+def LiveComposite(size, *args, **properties):
+ """
+ :args: disp_imagelike
+
+ This creates a new displayable of `size`, by compositing other
+ displayables. `size` is a (width, height) tuple.
+
+ The remaining positional arguments are used to place images inside
+ the LiveComposite. The remaining positional arguments should come
+ in groups of two, with the first member of each group an (x, y)
+ tuple, and the second member of a group is a displayable that
+ is composited at that position.
+
+ Displayables are composited from back to front.
+
+ ::
+
+ image eileen composite = LiveComposite(
+ (300, 600),
+ (0, 0), "body.png",
+ (0, 0), "clothes.png",
+ (50, 50), "expression.png")
+ """
+
+ properties.setdefault('style', 'image_placement')
+
+ width, height = size
+
+ rv = Fixed(xmaximum=width, ymaximum=height, xminimum=width, yminimum=height, **properties)
+
+ if len(args) % 2 != 0:
+ raise Exception("LiveComposite requires an odd number of arguments.")
+
+ for pos, widget in zip(args[0::2], args[1::2]):
+ xpos, ypos = pos
+ rv.add(Position(renpy.easy.displayable(widget),
+ xpos=xpos, xanchor=0, ypos=ypos, yanchor=0))
+
+ return rv
+
+class Position(Container):
+ """
+ Controls the placement of a displayable on the screen, using
+ supplied position properties. This is the non-curried form of
+ Position, which should be used when the user has directly created
+ the displayable that will be shown on the screen.
+ """
+
+ def __init__(self, child, style='image_placement', **properties):
+ """
+ @param child: The child that is being laid out.
+
+ @param style: The base style of this position.
+
+ @param properties: Position properties that control where the
+ child of this widget is placed.
+ """
+
+ super(Position, self).__init__(style=style, **properties)
+ self.add(child)
+
+ def render(self, width, height, st, at):
+
+ surf = render(self.child, width, height, st, at)
+
+ self.offsets = [ (0, 0) ]
+
+ rv = renpy.display.render.Render(surf.width, surf.height)
+ rv.blit(surf, (0, 0))
+
+ return rv
+
+ def get_placement(self):
+
+ xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.child.get_placement()
+
+ v = self.style.xpos
+ if v is not None:
+ xpos = v
+
+ v = self.style.ypos
+ if v is not None:
+ ypos = v
+
+ v = self.style.xanchor
+ if v is not None:
+ xanchor = v
+
+ v = self.style.yanchor
+ if v is not None:
+ yanchor = v
+
+ v = self.style.xoffset
+ if v is not None:
+ xoffset = v
+
+ v = self.style.yoffset
+ if v is not None:
+ yoffset = v
+
+ v = self.style.subpixel
+ if v is not None:
+ subpixel = v
+
+ return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+
+class Grid(Container):
+ """
+ A grid is a widget that evenly allocates space to its children.
+ The child widgets should not be greedy, but should instead be
+ widgets that only use part of the space available to them.
+ """
+
+ def __init__(self, cols, rows, padding=None,
+ transpose=False,
+ style='grid', **properties):
+ """
+ @param cols: The number of columns in this widget.
+
+ @params rows: The number of rows in this widget.
+
+ @params transpose: True if the grid should be transposed.
+ """
+
+ if padding is not None:
+ properties.setdefault('spacing', padding)
+
+ super(Grid, self).__init__(style=style, **properties)
+
+ cols = int(cols)
+ rows = int(rows)
+
+ self.cols = cols
+ self.rows = rows
+
+ self.transpose = transpose
+
+ def render(self, width, height, st, at):
+
+ # For convenience and speed.
+ padding = self.style.spacing
+ cols = self.cols
+ rows = self.rows
+
+ if len(self.children) != cols * rows:
+ if len(self.children) < cols * rows:
+ raise Exception("Grid not completely full.")
+ else:
+ raise Exception("Grid overfull.")
+
+ # If necessary, transpose the grid (kinda hacky, but it works here.)
+ if self.transpose:
+ self.transpose = False
+
+ old_children = self.children[:]
+
+ for y in range(0, rows):
+ for x in range(0, cols):
+ self.children[x + y * cols] = old_children[ y + x * rows ]
+
+
+ # Now, start the actual rendering.
+
+ renwidth = width
+ renheight = height
+
+ if self.style.xfill:
+ renwidth = (width - (cols - 1) * padding) / cols
+ if self.style.yfill:
+ renheight = (height - (rows - 1) * padding) / rows
+
+ renders = [ render(i, renwidth, renheight, st, at) for i in self.children ]
+ sizes = [ i.get_size() for i in renders ]
+
+ cwidth = 0
+ cheight = 0
+
+ for w, h in sizes:
+ cwidth = max(cwidth, w)
+ cheight = max(cheight, h)
+
+ if self.style.xfill:
+ cwidth = renwidth
+
+ if self.style.yfill:
+ cheight = renheight
+
+ width = cwidth * cols + padding * (cols - 1)
+ height = cheight * rows + padding * (rows - 1)
+
+ rv = renpy.display.render.Render(width, height)
+
+ self.offsets = [ ]
+
+ for y in range(0, rows):
+ for x in range(0, cols):
+
+ child = self.children[ x + y * cols ]
+ surf = renders[x + y * cols]
+
+ xpos = x * (cwidth + padding)
+ ypos = y * (cheight + padding)
+
+ offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
+ self.offsets.append(offset)
+
+ return rv
+
+class IgnoreLayers(Exception):
+ """
+ Raise this to have the event ignored by layers, but reach the
+ underlay.
+ """
+
+ pass
+
+class MultiBox(Container):
+
+ layer_name = None
+ first = True
+ order_reverse = False
+
+ def __init__(self, spacing=None, layout=None, style='default', **properties):
+
+ if spacing is not None:
+ properties['spacing'] = spacing
+
+ super(MultiBox, self).__init__(style=style, **properties)
+
+ self.default_layout = layout
+
+ # The start and animation times for children of this
+ # box.
+ self.start_times = [ ]
+ self.anim_times = [ ]
+
+ # A map from layer name to the widget corresponding to
+ # that layer.
+ self.layers = None
+
+ # The scene list for this widget.
+ self.scene_list = None
+
+ def add(self, widget, start_time=None, anim_time=None): # W0221
+ super(MultiBox, self).add(widget)
+ self.start_times.append(start_time)
+ self.anim_times.append(anim_time)
+
+ def append_scene_list(self, l):
+
+ for sle in l:
+ self.add(sle.displayable, sle.show_time, sle.animation_time)
+
+ if self.scene_list is None:
+ self.scene_list = [ ]
+
+ self.scene_list.extend(l)
+
+ def render(self, width, height, st, at):
+
+ # Do we need to adjust the child times due to our being a layer?
+ if self.layer_name or (self.layers is not None):
+ adjust_times = True
+ else:
+ adjust_times = False
+
+ xminimum = self.style.xminimum
+ if xminimum is not None:
+ width = max(width, scale(xminimum, width))
+
+ yminimum = self.style.yminimum
+ if yminimum is not None:
+ height = max(height, scale(yminimum, height))
+
+ if self.first:
+
+ self.first = False
+
+ if adjust_times:
+
+ it = renpy.game.interface.interact_time
+
+ self.start_times = [ i or it for i in self.start_times ]
+ self.anim_times = [ i or it for i in self.anim_times ]
+
+ layout = self.style.box_layout
+
+ if layout is None:
+ layout = self.default_layout
+
+ self.layout = layout # W0201
+
+ else:
+ layout = self.layout
+
+
+ # Handle time adjustment, store the results in csts and cats.
+ if adjust_times:
+ t = renpy.game.interface.frame_time
+
+ csts = [ t - start for start in self.start_times ]
+ cats = [ t - anim for anim in self.anim_times ]
+
+ else:
+ csts = [ st ] * len(self.children)
+ cats = [ at ] * len(self.children)
+
+ offsets = [ ]
+
+ if layout == "fixed":
+
+ rv = None
+
+ if self.style.order_reverse:
+ iterator = list(zip(reversed(self.children), reversed(csts), reversed(cats)))
+ else:
+ iterator = list(zip(self.children, csts, cats))
+
+ for child, cst, cat in iterator:
+
+ surf = render(child, width, height, cst, cat)
+
+ if rv is None:
+
+ if self.style.fit_first:
+ sw, sh = surf.get_size()
+ width = min(width, sw)
+ height = min(height, sh)
+
+
+ rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)
+
+ if surf:
+ offset = child.place(rv, 0, 0, width, height, surf)
+ offsets.append(offset)
+ else:
+ offsets.append((0, 0))
+
+ if rv is None:
+ rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)
+
+ if self.style.order_reverse:
+ offsets.reverse()
+
+ self.offsets = offsets
+
+ return rv
+
+ # If we're here, we have a box, either horizontal or vertical. Which is good,
+ # as we can share some code between boxes.
+
+
+ spacing = self.style.spacing
+ first_spacing = self.style.first_spacing
+
+ if first_spacing is None:
+ first_spacing = spacing
+
+ spacings = [ first_spacing ] + [ spacing ] * (len(self.children) - 1)
+
+ box_wrap = self.style.box_wrap
+
+ xfill = self.style.xfill
+ yfill = self.style.yfill
+
+ # The shared height and width of the current line. The line_height must
+ # be 0 for a vertical box, and the line_width must be 0 for a horizontal
+ # box.
+ line_width = 0
+ line_height = 0
+
+ # The children to layout.
+ children = list(self.children)
+ if self.style.box_reverse:
+ children.reverse()
+ spacings.reverse()
+
+ # a list of (child, x, y, w, h, surf) tuples that are turned into
+ # calls to child.place().
+ placements = [ ]
+
+ # The maximum x and y.
+ maxx = 0
+ maxy = 0
+
+ def layout_line(line, xfill, yfill):
+ """
+ Lays out a single line.
+
+ `line` a list of (child, x, y, surf) tuples.
+ `xfill` the amount of space to add in the x direction.
+ `yfill` the amount of space to add in the y direction.
+ """
+
+ xfill = max(0, xfill)
+ yfill = max(0, yfill)
+
+ if line:
+ xperchild = xfill / len(line)
+ yperchild = yfill / len(line)
+ else:
+ xperchild = 0
+ yperchild = 0
+
+ maxxout = maxx
+ maxyout = maxy
+
+ for i, (child, x, y, surf) in enumerate(line):
+ sw, sh = surf.get_size()
+ sw = max(line_width, sw)
+ sh = max(line_height, sh)
+
+ x += i * xperchild
+ y += i * yperchild
+
+ sw += xperchild
+ sh += yperchild
+
+ placements.append((child, x, y, sw, sh, surf))
+
+ maxxout = max(maxxout, x + sw)
+ maxyout = max(maxyout, y + sh)
+
+ return maxxout, maxyout
+
+ x = 0
+ y = 0
+
+ full_width = False
+ full_height = False
+
+ if layout == "horizontal":
+
+ full_height = yfill
+
+ line_height = 0
+ line = [ ]
+ remwidth = width
+
+ for d, padding, cst, cat in zip(children, spacings, csts, cats):
+
+ if box_wrap:
+ rw = width
+ else:
+ rw = remwidth
+
+ surf = render(d, rw, height - y, cst, cat)
+ sw, sh = surf.get_size()
+
+ if box_wrap and remwidth - sw - padding <= 0 and line:
+ maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)
+
+ y += line_height
+ x = 0
+ line_height = 0
+ remwidth = width
+ line = [ ]
+
+
+ line.append((d, x, y, surf))
+ line_height = max(line_height, sh)
+ x += sw + padding
+ remwidth -= (sw + padding)
+
+ maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)
+
+
+ elif layout == "vertical":
+
+ full_width = xfill
+
+ line_width = 0
+ line = [ ]
+ remheight = height
+
+ for d, padding, cst, cat in zip(children, spacings, csts, cats):
+
+ if box_wrap:
+ rh = height
+ else:
+ rh = remheight
+
+ surf = render(d, width - x, rh, cst, cat)
+ sw, sh = surf.get_size()
+
+ if box_wrap and remheight - sh - padding <= 0:
+ maxx, maxy = layout_line(line, 0, remheight if yfill else 0)
+
+ x += line_width
+ y = 0
+ line_width = 0
+ remheight = height
+ line = [ ]
+
+ line.append((d, x, y, surf))
+ line_width = max(line_width, sw)
+ y += sh + padding
+ remheight -= (sh + padding)
+
+ maxx, maxy = layout_line(line, 0, remheight if yfill else 0)
+
+ # Back to the common for vertical and horizontal.
+
+ if not xfill:
+ width = maxx
+
+ if not yfill:
+ height = maxy
+
+ rv = renpy.display.render.Render(width, height)
+
+ if self.style.box_reverse ^ self.style.order_reverse:
+ placements.reverse()
+
+ for child, x, y, w, h, surf in placements:
+ if full_width:
+ w = width
+ if full_height:
+ h = height
+
+ offset = child.place(rv, x, y, w, h, surf)
+ offsets.append(offset)
+
+ if self.style.order_reverse:
+ offsets.reverse()
+
+ self.offsets = offsets
+
+ return rv
+
+
+ def event(self, ev, x, y, st):
+
+
+ children_offsets = list(zip(self.children, self.offsets, self.start_times))
+
+ if not self.style.order_reverse:
+ children_offsets.reverse()
+
+ try:
+
+ for i, (xo, yo), t in children_offsets:
+
+ if t is None:
+ cst = st
+ else:
+ cst = renpy.game.interface.event_time - t
+
+ rv = i.event(ev, x - xo, y - yo, cst)
+ if rv is not None:
+ return rv
+
+ except IgnoreLayers:
+ if self.layers:
+ return None
+ else:
+ raise
+
+ return None
+
+def Fixed(**properties):
+ return MultiBox(layout='fixed', **properties)
+
+class SizeGroup(renpy.object.Object):
+
+ def __init__(self):
+
+ super(SizeGroup, self).__init__()
+
+ self.members = [ ]
+ self._width = None
+ self.computing_width = False
+
+ def width(self, width, height, st, at):
+ if self._width is not None:
+ return self._width
+
+ if self.computing_width:
+ return 0
+
+ self.computing_width = True
+
+ maxwidth = 0
+
+ for i in self.members:
+ rend = renpy.display.render.render(i, width, height, st, at)
+ maxwidth = max(rend.width, maxwidth)
+ renpy.display.render.invalidate(i)
+
+ self._width = maxwidth
+ self.computing_width = False
+
+ return maxwidth
+
+
+size_groups = dict()
+
+class Window(Container):
+ """
+ A window that has padding and margins, and can place a background
+ behind its child. `child` is the child added to this
+ displayable. All other properties are as for the :ref:`Window`
+ screen language statement.
+ """
+
+ def __init__(self, child, style='window', **properties):
+
+ super(Window, self).__init__(style=style, **properties)
+ if child is not None:
+ self.add(child)
+
+ def visit(self):
+ return [ self.style.background ] + self.children
+
+ def get_child(self):
+ return self.style.child or self.child
+
+ def per_interact(self):
+ size_group = self.style.size_group
+
+ if size_group:
+ group = size_groups.get(size_group, None)
+ if group is None:
+ group = size_groups[size_group] = SizeGroup()
+
+ group.members.append(self)
+
+ def predict_one(self):
+ # Child will be predicted by visiting.
+
+ pd = renpy.display.predict.displayable
+ style = self.style
+
+ pd(style.insensitive_background)
+ pd(style.idle_background)
+ pd(style.hover_background)
+ pd(style.selected_idle_background)
+ pd(style.selected_hover_background)
+
+ pd(style.insensitive_child)
+ pd(style.idle_child)
+ pd(style.hover_child)
+ pd(style.selected_idle_child)
+ pd(style.selected_hover_child)
+
+ pd(style.insensitive_foreground)
+ pd(style.idle_foreground)
+ pd(style.hover_foreground)
+ pd(style.selected_idle_foreground)
+ pd(style.selected_hover_foreground)
+
+ def render(self, width, height, st, at):
+
+ # save some typing.
+ style = self.style
+
+ xminimum = scale(style.xminimum, width)
+ yminimum = scale(style.yminimum, height)
+
+ size_group = self.style.size_group
+ if size_group and size_group in size_groups:
+ xminimum = max(xminimum, size_groups[size_group].width(width, height, st, at))
+
+ left_margin = scale(style.left_margin, width)
+ left_padding = scale(style.left_padding, width)
+
+ right_margin = scale(style.right_margin, width)
+ right_padding = scale(style.right_padding, width)
+
+ top_margin = scale(style.top_margin, height)
+ top_padding = scale(style.top_padding, height)
+
+ bottom_margin = scale(style.bottom_margin, height)
+ bottom_padding = scale(style.bottom_padding, height)
+
+ # c for combined.
+ cxmargin = left_margin + right_margin
+ cymargin = top_margin + bottom_margin
+
+ cxpadding = left_padding + right_padding
+ cypadding = top_padding + bottom_padding
+
+ child = self.get_child()
+
+ # Render the child.
+ surf = render(child,
+ width - cxmargin - cxpadding,
+ height - cymargin - cypadding,
+ st, at)
+
+ sw, sh = surf.get_size()
+
+ # If we don't fill, shrink our size to fit.
+
+ if not style.xfill:
+ width = max(cxmargin + cxpadding + sw, xminimum)
+
+ if not style.yfill:
+ height = max(cymargin + cypadding + sh, yminimum)
+
+ rv = renpy.display.render.Render(width, height)
+
+ # Draw the background. The background should render at exactly the
+ # requested size. (That is, be a Frame or a Solid).
+ if style.background:
+ bw = width - cxmargin
+ bh = height - cymargin
+
+ back = render(style.background, bw, bh, st, at)
+
+ style.background.place(rv, left_margin, top_margin, bw, bh, back, main=False)
+
+ offsets = child.place(rv,
+ left_margin + left_padding,
+ top_margin + top_padding,
+ width - cxmargin - cxpadding,
+ height - cymargin - cypadding,
+ surf)
+
+ # Draw the foreground. The background should render at exactly the
+ # requested size. (That is, be a Frame or a Solid).
+ if style.foreground:
+ bw = width - cxmargin
+ bh = height - cymargin
+
+ back = render(style.foreground, bw, bh, st, at)
+
+ style.foreground.place(rv, left_margin, top_margin, bw, bh, back, main=False)
+
+ self.offsets = [ offsets ]
+
+ self.window_size = width, height # W0201
+
+ return rv
+
+
+def dynamic_displayable_compat(st, at, expr):
+ child = renpy.python.py_eval(expr)
+ return child, None
+
+class DynamicDisplayable(renpy.display.core.Displayable):
+ """
+ :doc: disp_dynamic
+
+ A displayable that can change its child based on a Python
+ function, over the course of an interaction.
+
+ `function`
+ A function that is called with the arguments:
+
+ * The amount of time the displayable has been shown for.
+ * The amount of time any displayable with the same tag has been shown for.
+ * Any positional or keyword arguments supplied to DynamicDisplayable.
+
+ and should return a (d, redraw) tuple, where:
+
+ * `d` is a displayable to show.
+ * `redraw` is the amount of time to wait before calling the
+ function again, or None to not call the function again
+ before the start of the next interaction.
+
+ `function` is called at the start of every interaction.
+
+ As a special case, `function` may also be a python string that evaluates
+ to a displayable. In that case, function is run once per interaction.
+
+ ::
+
+ # If tooltip is not empty, shows it in a text. Otherwise,
+ # show Null. Checks every tenth of a second to see if the
+ # tooltip has been updated.
+ init python:
+ def show_tooltip(st, at):
+ if tooltip:
+ return tooltip, .1
+ else:
+ return Null()
+
+ image tooltipper = DynamicDisplayable(show_tooltip)
+
+ """
+
+ nosave = [ 'child' ]
+
+ def after_setstate(self):
+ self.child = None
+
+ def __init__(self, function, *args, **kwargs):
+
+ super(DynamicDisplayable, self).__init__()
+ self.child = None
+
+ if isinstance(function, str):
+ args = ( function, )
+ kwargs = { }
+ function = dynamic_displayable_compat
+
+ self.predict_function = kwargs.pop("_predict_function", None)
+ self.function = function
+ self.args = args
+ self.kwargs = kwargs
+ self.st = 0
+ self.at = 0
+
+ def visit(self):
+ return [ ]
+
+ def per_interact(self):
+ child, _ = self.function(self.st, self.at, *self.args, **self.kwargs)
+ child = renpy.easy.displayable(child)
+ child.visit_all(lambda a : a.per_interact())
+
+ if child is not self.child:
+ renpy.display.render.redraw(self, 0)
+ self.child = child
+
+ def render(self, w, h, st, at):
+
+ self.st = st
+ self.at = at
+
+ child, redraw = self.function(st, at, *self.args, **self.kwargs)
+ child = renpy.easy.displayable(child)
+ child.visit_all(lambda c : c.per_interact())
+
+ self.child = child
+
+ if redraw is not None:
+ renpy.display.render.redraw(self, redraw)
+
+ return renpy.display.render.render(self.child, w, h, st, at)
+
+ def predict_one(self):
+ if not self.predict_function:
+ return
+
+ for i in self.predict_function(*self.args, **self.kwargs):
+ if i is not None:
+ renpy.display.predict.displayable(i)
+
+ def get_placement(self):
+ if not self.child:
+ self.per_interact()
+
+ return self.child.get_placement()
+
+
+ def event(self, ev, x, y, st):
+ if self.child:
+ return self.child.event(ev, x, y, st)
+
+# This chooses the first member of switch that's being shown on the
+# given layer.
+def condition_switch_pick(switch):
+ for cond, d in switch:
+ if cond is None or renpy.python.py_eval(cond):
+ return d
+
+ raise Exception("Switch could not choose a displayable.")
+
+def condition_switch_show(st, at, switch):
+ return condition_switch_pick(switch), None
+
+def condition_switch_predict(switch):
+
+ if renpy.game.lint:
+ return [ d for _cond, d in switch ]
+
+ return [ condition_switch_pick(switch) ]
+
+def ConditionSwitch(*args, **kwargs):
+ """
+ :doc: disp_dynamic
+
+ This is a displayable that changes what it is showing based on
+ python conditions. The positional argument should be given in
+ groups of two, where each group consists of:
+
+ * A string containing a python condition.
+ * A displayable to use if the condition is true.
+
+ The first true condition has its displayable shown, at least
+ one condition should always be true.
+
+ ::
+
+ image jill = ConditionSwitch(
+ "jill_beers > 4", "jill_drunk.png",
+ "True", "jill_sober.png")
+ """
+
+ kwargs.setdefault('style', 'default')
+
+ switch = [ ]
+
+ if len(args) % 2 != 0:
+ raise Exception('ConditionSwitch takes an even number of arguments')
+
+ for cond, d in zip(args[0::2], args[1::2]):
+
+ d = renpy.easy.displayable(d)
+ switch.append((cond, d))
+
+ rv = DynamicDisplayable(condition_switch_show,
+ switch,
+ _predict_function=condition_switch_predict)
+
+ return Position(rv, **kwargs)
+
+
+def ShowingSwitch(*args, **kwargs):
+ """
+ :doc: disp_dynamic
+
+ This is a displayable that changes what it is showing based on the
+ images are showing on the screen. The positional argument should
+ be given in groups of two, where each group consists of:
+
+ * A string giving an image name, or None to indicate the default.
+ * A displayable to use if the condition is true.
+
+ A default image should be specified.
+
+ One use of ShowingSwitch is to have side images change depending on
+ the current emotion of a character. For example::
+
+ define e = Character("Eileen",
+ show_side_image=ShowingSwitch(
+ "eileen happy", Image("eileen_happy_side.png", xalign=1.0, yalign=1.0),
+ "eileen vhappy", Image("eileen_vhappy_side.png", xalign=1.0, yalign=1.0),
+ None, Image("eileen_happy_default.png", xalign=1.0, yalign=1.0),
+ )
+ )
+ """
+
+ layer = kwargs.pop('layer', 'master')
+
+ if len(args) % 2 != 0:
+ raise Exception('ShowingSwitch takes an even number of positional arguments')
+
+ condargs = [ ]
+
+
+ for name, d in zip(args[0::2], args[1::2]):
+ if name is not None:
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+ cond = "renpy.showing(%r, layer=%r)" % (name, layer)
+ else:
+ cond = None
+
+
+ condargs.append(cond)
+ condargs.append(d)
+
+ return ConditionSwitch(*condargs, **kwargs)
+
+
+class IgnoresEvents(Container):
+
+ def __init__(self, child, **properties):
+ super(IgnoresEvents, self).__init__(**properties)
+ self.add(child)
+
+ def render(self, w, h, st, at):
+ cr = renpy.display.render.render(self.child, w, h, st, at)
+ cw, ch = cr.get_size()
+ rv = renpy.display.render.Render(cw, ch)
+ rv.blit(cr, (0, 0), focus=False)
+
+ return rv
+
+ def get_placement(self):
+ return self.child.get_placement()
+
+ # Ignores events.
+ def event(self, ev, x, y, st):
+ return None
+
+def edgescroll_proportional(n):
+ """
+ An edgescroll function that causes the move speed to be proportional
+ from the edge distance.
+ """
+ return n
+
+class Viewport(Container):
+
+ __version__ = 3
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
+ self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
+ self.set_adjustments = False
+ self.mousewheel = False
+ self.draggable = False
+ self.width = 0
+ self.height = 0
+
+ if version < 2:
+ self.drag_position = None
+
+ if version < 3:
+ self.edge_size = False
+ self.edge_speed = False
+ self.edge_function = None
+ self.edge_xspeed = 0
+ self.edge_yspeed = 0
+ self.edge_last_st = None
+
+ def __init__(self,
+ child=None,
+ child_size=(None, None),
+ offsets=(None, None),
+ xadjustment=None,
+ yadjustment=None,
+ set_adjustments=True,
+ mousewheel=False,
+ draggable=False,
+ edgescroll=None,
+ style='viewport',
+ xinitial=None,
+ yinitial=None,
+ replaces=None,
+ **properties):
+
+ super(Viewport, self).__init__(style=style, **properties)
+ if child is not None:
+ self.add(child)
+
+ if xadjustment is None:
+ self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
+ else:
+ self.xadjustment = xadjustment
+
+ if yadjustment is None:
+ self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
+ else:
+ self.yadjustment = yadjustment
+
+
+ if isinstance(replaces, Viewport):
+ self.xadjustment.range = replaces.xadjustment.range
+ self.yadjustment.range = replaces.yadjustment.range
+ self.xadjustment.value = replaces.xadjustment.value
+ self.yadjustment.value = replaces.yadjustment.value
+ self.xoffset = replaces.xoffset
+ self.yoffset = replaces.yoffset
+ self.drag_position = replaces.drag_position
+ else:
+ self.xoffset = offsets[0] if (offsets[0] is not None) else xinitial
+ self.yoffset = offsets[1] if (offsets[1] is not None) else yinitial
+ self.drag_position = None
+
+ if self.xadjustment.adjustable is None:
+ self.xadjustment.adjustable = True
+
+ if self.yadjustment.adjustable is None:
+ self.yadjustment.adjustable = True
+
+ self.set_adjustments = set_adjustments
+
+ self.child_width, self.child_height = child_size
+
+ self.mousewheel = mousewheel
+ self.draggable = draggable
+
+ self.width = 0
+ self.height = 0
+
+ # The speed at which we scroll in the x and y directions, in pixels
+ # per second.
+ self.edge_xspeed = 0
+ self.edge_yspeed = 0
+
+ # The last time we edgescrolled.
+ self.edge_last_st = None
+
+ if edgescroll is not None:
+
+ # The size of the edges that trigger scrolling.
+ self.edge_size = edgescroll[0]
+
+ # How far from the edge we can scroll.
+ self.edge_speed = edgescroll[1]
+
+ if len(edgescroll) >= 3:
+ self.edge_function = edgescroll[2]
+ else:
+ self.edge_function = edgescroll_proportional
+
+ else:
+ self.edge_size = 0
+ self.edge_speed = 0
+ self.edge_function = edgescroll_proportional
+
+
+ def per_interact(self):
+ self.xadjustment.register(self)
+ self.yadjustment.register(self)
+
+ def render(self, width, height, st, at):
+
+ self.width = width
+ self.height = height
+
+ child_width = self.child_width or width
+ child_height = self.child_height or height
+
+ surf = render(self.child, child_width, child_height, st, at)
+
+ cw, ch = surf.get_size()
+
+ # width = min(cw, width)
+ # height = min(ch, height)
+
+ if self.set_adjustments:
+ self.xadjustment.range = max(cw - width, 0)
+ self.xadjustment.page = width
+ self.yadjustment.range = max(ch - height, 0)
+ self.yadjustment.page = height
+
+ if self.xoffset is not None:
+ if isinstance(self.xoffset, int):
+ value = self.xoffset
+ else:
+ value = max(cw - width, 0) * self.xoffset
+
+ self.xadjustment.value = value
+
+ if self.yoffset is not None:
+ if isinstance(self.yoffset, int):
+ value = self.yoffset
+ else:
+ value = max(ch - height, 0) * self.yoffset
+
+ self.yadjustment.value = value
+
+ if self.edge_size and self.edge_last_st and (self.edge_xspeed or self.edge_yspeed):
+
+ duration = max(st - self.edge_last_st, 0)
+ self.xadjustment.change(self.xadjustment.value + duration * self.edge_xspeed)
+ self.yadjustment.change(self.yadjustment.value + duration * self.edge_yspeed)
+
+ self.check_edge_redraw()
+
+ self.edge_last_st = st
+
+ cxo = -int(self.xadjustment.value)
+ cyo = -int(self.yadjustment.value)
+
+ self.offsets = [ (cxo, cyo) ]
+
+ rv = renpy.display.render.Render(width, height)
+ rv.blit(surf, (cxo, cyo))
+
+ return rv
+
+ def check_edge_redraw(self):
+ redraw = False
+
+ if (self.edge_xspeed > 0) and (self.xadjustment.value < self.xadjustment.range):
+ redraw = True
+ if (self.edge_xspeed < 0) and (self.xadjustment.value > 0):
+ redraw = True
+
+ if (self.edge_yspeed > 0) and (self.yadjustment.value < self.yadjustment.range):
+ redraw = True
+ if (self.edge_yspeed < 0) and (self.yadjustment.value > 0):
+ redraw = True
+
+ if redraw:
+ renpy.display.render.redraw(self, 0)
+
+
+ def event(self, ev, x, y, st):
+
+ self.xoffset = None
+ self.yoffset = None
+
+ rv = super(Viewport, self).event(ev, x, y, st)
+ if rv is not None:
+ return rv
+
+ if self.draggable and renpy.display.focus.get_grab() == self:
+
+ oldx, oldy = self.drag_position
+ dx = x - oldx
+ dy = y - oldy
+
+ self.xadjustment.change(self.xadjustment.value - dx)
+ self.yadjustment.change(self.yadjustment.value - dy)
+
+ self.drag_position = (x, y) # W0201
+
+ if renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
+ renpy.display.focus.set_grab(None)
+ raise renpy.display.core.IgnoreEvent()
+
+ if not ((0 <= x < self.width) and (0 <= y <= self.height)):
+ return
+
+ if self.mousewheel:
+
+ if renpy.display.behavior.map_event(ev, 'viewport_up'):
+ rv = self.yadjustment.change(self.yadjustment.value - self.yadjustment.step)
+ if rv is not None:
+ return rv
+ else:
+ raise renpy.display.core.IgnoreEvent()
+
+ if renpy.display.behavior.map_event(ev, 'viewport_down'):
+ rv = self.yadjustment.change(self.yadjustment.value + self.yadjustment.step)
+ if rv is not None:
+ return rv
+ else:
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.draggable:
+
+ if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
+ self.drag_position = (x, y)
+ renpy.display.focus.set_grab(self)
+ raise renpy.display.core.IgnoreEvent()
+
+ if self.edge_size:
+
+ def speed(n, zero, one):
+ """
+ Given a position `n`, computes the speed. The speed is 0.0
+ when `n` == `zero`, 1.0 when `n` == `one`, and linearly
+ interpolated when between.
+
+ Returns 0.0 when outside the bounds - in either direction.
+ """
+
+ n = 1.0 * (n - zero) / (one - zero)
+ if n < 0.0:
+ return 0.0
+ if n > 1.0:
+ return 0.0
+
+ return n
+
+ xspeed = speed(x, self.width - self.edge_size, self.width)
+ xspeed -= speed(x, self.edge_size, 0)
+ self.edge_xspeed = self.edge_speed * self.edge_function(xspeed)
+
+ yspeed = speed(y, self.height - self.edge_size, self.height)
+ yspeed -= speed(y, self.edge_size, 0)
+ self.edge_yspeed = self.edge_speed * self.edge_function(yspeed)
+
+ if xspeed or yspeed:
+ self.check_edge_redraw()
+ if self.edge_last_st is None:
+ self.edge_last_st = st
+ else:
+ self.edge_last_st = None
+
+ return None
+
+ def set_xoffset(self, offset):
+ self.xoffset = offset
+ renpy.display.render.redraw(self, 0)
+
+ def set_yoffset(self, offset):
+ self.yoffset = offset
+ renpy.display.render.redraw(self, 0)
+
+def LiveCrop(rect, child, **properties):
+ """
+ :doc: disp_imagelike
+
+ This created a displayable by cropping `child` to `rect`, where
+ `rect` is an (x, y, width, height) tuple. ::
+
+ image eileen cropped = LiveCrop((0, 0, 300, 300), "eileen happy")
+ """
+
+ x, y, w, h = rect
+
+ return Viewport(child, offsets=(x, y), xmaximum=w, ymaximum=h, **properties)
+
+class Side(Container):
+
+ possible_positions = set([ 'tl', 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'c'])
+
+ def after_setstate(self):
+ self.sized = False
+
+ def __init__(self, positions, style='side', **properties):
+
+ super(Side, self).__init__(style=style, **properties)
+
+ if isinstance(positions, str):
+ positions = positions.split()
+
+ for i in positions:
+ if not i in Side.possible_positions:
+ raise Exception("Side used with impossible position '%s'." % (i,))
+
+ self.positions = tuple(positions)
+ self.sized = False
+
+ def render(self, width, height, st, at):
+
+ pos_d = { }
+ pos_i = { }
+
+ for i, (pos, d) in enumerate(zip(self.positions, self.children)):
+ pos_d[pos] = d
+ pos_i[pos] = i
+
+ # Figure out the size of each widget (and hence where the
+ # widget needs to be placed).
+
+ if not self.sized:
+ self.sized = True
+
+ # Deal with various spacings.
+ spacing = self.style.spacing
+
+ def spacer(a, b, c, axis):
+ if (a in pos_d) or (b in pos_d) or (c in pos_d):
+ return spacing, axis - spacing
+ else:
+ return 0, axis
+
+ self.left_space, width = spacer('tl', 'l', 'bl', width) # W0201
+ self.right_space, width = spacer('tr', 'r', 'br', width) # W0201
+ self.top_space, height = spacer('tl', 't', 'tr', height) # W0201
+ self.bottom_space, height = spacer('bl', 'b', 'br', height) # W0201
+
+ # The sizes of the various borders.
+ left = 0
+ right = 0
+ top = 0
+ bottom = 0
+ cwidth = 0
+ cheight = 0
+
+ def sizeit(pos, width, height, owidth, oheight):
+ if pos not in pos_d:
+ return owidth, oheight
+
+ rend = render(pos_d[pos], width, height, st, at)
+ rv = max(owidth, rend.width), max(oheight, rend.height)
+ rend.kill()
+ return rv
+
+ cwidth, cheight = sizeit('c', width, height, 0, 0)
+ cwidth, top = sizeit('t', cwidth, height, cwidth, top)
+ cwidth, bottom = sizeit('b', cwidth, height, cwidth, bottom)
+ left, cheight = sizeit('l', width, cheight, left, cheight)
+ right, cheight = sizeit('r', width, cheight, right, cheight)
+
+ left, top = sizeit('tl', left, top, left, top)
+ left, bottom = sizeit('bl', left, bottom, left, bottom)
+ right, top = sizeit('tr', right, top, right, top)
+ right, bottom = sizeit('br', right, bottom, right, bottom)
+
+ self.cwidth = cwidth # W0201
+ self.cheight = cheight # W0201
+
+ self.top = top # W0201
+ self.bottom = bottom # W0201
+ self.left = left # W0201
+ self.right = right # W0201
+
+ else:
+ cwidth = self.cwidth
+ cheight = self.cheight
+ top = self.top
+ bottom = self.bottom
+ left = self.left
+ right = self.right
+
+ # Now, place everything onto the render.
+
+ self.offsets = [ None ] * len(self.children)
+
+ lefts = self.left_space
+ rights = self.right_space
+ tops = self.top_space
+ bottoms = self.bottom_space
+
+
+ cwidth = min(cwidth, width - left - lefts - right - rights)
+ cheight = min(cheight, height - top - tops - bottom - bottoms)
+
+ rv = renpy.display.render.Render(left + lefts + cwidth + rights + right,
+ top + tops + cheight + bottoms + bottom)
+
+ def place(pos, x, y, w, h):
+
+ if pos not in pos_d:
+ return
+
+ d = pos_d[pos]
+ i = pos_i[pos]
+ rend = render(d, w, h, st, at)
+ self.offsets[i] = pos_d[pos].place(rv, x, y, w, h, rend)
+
+ col1 = 0
+ col2 = left + lefts
+ col3 = left + lefts + cwidth + rights
+
+ row1 = 0
+ row2 = top + tops
+ row3 = top + tops + cheight + bottoms
+
+ place('c', col2, row2, cwidth, cheight)
+
+ place('t', col2, row1, cwidth, top)
+ place('r', col3, row2, right, cheight)
+ place('b', col2, row3, cwidth, bottom)
+ place('l', col1, row2, left, cheight)
+
+ place('tl', col1, row1, left, top)
+ place('tr', col3, row1, right, top)
+ place('br', col3, row3, right, bottom)
+ place('bl', col1, row3, left, bottom)
+
+ return rv
+
+class Alpha(renpy.display.core.Displayable):
+ def __init__(self, start, end, time, child=None, repeat=False, bounce=False,
+ anim_timebase=False, time_warp=None, **properties):
+
+ super(Alpha, self).__init__(**properties)
+
+ self.start = start
+ self.end = end
+ self.time = time
+ self.child = renpy.easy.displayable(child)
+ self.repeat = repeat
+ self.anim_timebase = anim_timebase
+ self.time_warp = time_warp
+
+ def visit(self):
+ return [ self.child ]
+
+ def render(self, height, width, st, at):
+ if self.anim_timebase:
+ t = at
+ else:
+ t = st
+
+ if self.time:
+ done = min(t / self.time, 1.0)
+ else:
+ done = 1.0
+
+ if renpy.game.less_updates:
+ done = 1.0
+ elif self.repeat:
+ done = done % 1.0
+ renpy.display.render.redraw(self, 0)
+ elif done != 1.0:
+ renpy.display.render.redraw(self, 0)
+
+ if self.time_warp:
+ done = self.time_warp(done)
+
+ alpha = self.start + done * (self.end - self.start)
+
+ rend = renpy.display.render.render(self.child, height, width, st, at)
+
+ w, h = rend.get_size()
+ rv = renpy.display.render.Render(w, h)
+ rv.blit(rend, (0, 0))
+ rv.alpha = alpha
+
+ return rv
+
+
+class AdjustTimes(Container):
+
+ def __init__(self, child, start_time, anim_time, **properties):
+ super(AdjustTimes, self).__init__(**properties)
+
+ self.start_time = start_time
+ self.anim_time = anim_time
+
+ self.add(child)
+
+ def render(self, w, h, st, at):
+
+ if self.start_time is None:
+ self.start_time = renpy.game.interface.frame_time
+
+ if self.anim_time is None:
+ self.anim_time = renpy.game.interface.frame_time
+
+ st = renpy.game.interface.frame_time - self.start_time
+ at = renpy.game.interface.frame_time - self.anim_time
+
+ cr = renpy.display.render.render(self.child, w, h, st, at)
+ cw, ch = cr.get_size()
+ rv = renpy.display.render.Render(cw, ch)
+ rv.blit(cr, (0, 0))
+
+ self.offsets = [ (0, 0) ]
+
+ return rv
+
+ def get_placement(self):
+ return self.child.get_placement()
+
+
+class LiveTile(Container):
+ """
+ :doc: disp_imagelike
+
+ Tiles `child` until it fills the area allocated to this displayable.
+
+ ::
+
+ image bg tile = LiveTile("bg.png")
+
+ """
+
+ def __init__(self, child, style='tile', **properties):
+ super(LiveTile, self).__init__(style=style, **properties)
+
+ self.add(child)
+
+ def render(self, width, height, st, at):
+
+ cr = renpy.display.render.render(self.child, width, height, st, at)
+ cw, ch = cr.get_size()
+ rv = renpy.display.render.Render(width, height)
+
+ width = int(width)
+ height = int(height)
+ cw = int(cw)
+ ch = int(ch)
+
+ for y in range(0, height, ch):
+ for x in range(0, width, cw):
+ rv.blit(cr, (x, y), focus=False)
+
+ return rv
diff --git a/unrpyc/renpy/display/minigame.py b/unrpyc/renpy/display/minigame.py
new file mode 100644
index 0000000..7469922
--- /dev/null
+++ b/unrpyc/renpy/display/minigame.py
@@ -0,0 +1,25 @@
+# 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.
+
+
+def Minigame(*args, **kwargs):
+ raise Exception("Minigame is no longer implemented.")
+
diff --git a/unrpyc/renpy/display/module.py b/unrpyc/renpy/display/module.py
new file mode 100644
index 0000000..1bf66e0
--- /dev/null
+++ b/unrpyc/renpy/display/module.py
@@ -0,0 +1,275 @@
+# 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 mediates access to the _renpy module, which is a C module that
+# allows us to enhance the feature set of pygame in a renpy specific way.
+
+VERSION = (6, 12, 0)
+
+import renpy.display
+import pygame; pygame # prevents pyflakes warning.
+
+import sys
+
+try:
+ import _renpy
+ version = _renpy.version()
+
+ if version != VERSION:
+ print("Found Ren'Py module version %s, while expecting %s." % (
+ ".".join(str(i) for i in version),
+ ".".join(str(i) for i in VERSION),
+ ))
+
+ print("Trying to run anyway, but you should expect errors.", file=sys.stderr)
+
+except:
+ print("The _renpy module was not found. Please read module/README.txt for", file=sys.stderr)
+ print("more information.", file=sys.stderr)
+
+ sys.exit(-1)
+
+def convert_and_call(function, src, dst, *args):
+ """
+ This calls the function with the source and destination
+ surface. The surfaces must have the same alpha.
+
+ If the surfaces are not 24 or 32 bits per pixel, or don't have the
+ same format, they are converted and then converted back.
+ """
+
+ # Now that all surfaces are 32bpp, this function doesn't do much
+ # of anything anymore.
+
+ if (dst.get_masks()[3] != 0) != (src.get_masks()[3] != 0):
+ raise Exception("Surface alphas do not match.")
+
+ function(src, dst, *args)
+
+
+def pixellate(src, dst, avgwidth, avgheight, outwidth, outheight):
+ """
+ This pixellates the source surface. First, every pixel in the
+ source surface is projected onto a virtual surface, such that
+ the average value of every avgwidth x avgheight pixels becomes
+ one virtual pixel. It then gets projected back onto the
+ destination surface at a ratio of one virtual pixel to every
+ outwidth x outheight destination pixels.
+
+ If either src or dst is not a 24 or 32 bit surface, they are
+ converted... but that may be a significant performance hit.
+
+ The two surfaces must either have the same alpha or no alpha.
+ """
+
+ convert_and_call(_renpy.pixellate,
+ src, dst,
+ avgwidth, avgheight,
+ outwidth, outheight)
+
+
+def scale(s, size):
+ """
+ Scales down the supplied pygame surface by the given X and Y
+ factors.
+
+ Always works, but may not be high quality.
+ """
+
+ d = renpy.display.pgrender.surface(size, True)
+
+ bilinear_scale(s, d)
+
+ return d
+
+
+# What we have here are a pair of tables mapping masks to byte offsets
+# for 24 and 32 bpp modes. We represent 0xff000000 as positive and negative
+# numbers so that it doesn't yield a warning, and so that it works on
+# 32 and 64 bit platforms.
+if sys.byteorder == 'big':
+ bo32 = { 255 : 3, 65280 : 2, 16711680 : 1, 4278190080 : 0, -16777216 : 0, }
+else:
+ bo32 = { 255 : 0, 65280 : 1, 16711680 : 2, 4278190080 : 3, -16777216 : 3, }
+
+bo_cache = None
+
+def byte_offset(src):
+ """
+ Given the surface src, returns a 4-tuple giving the byte offsets
+ for the red, green, blue, and alpha components of the pixels in
+ the surface. If a component doesn't exist, None is returned.
+ """
+
+ global bo_cache
+
+ if bo_cache is None:
+ bo_cache = [ bo32[i] for i in src.get_masks() ]
+
+ return bo_cache
+
+def endian_order(src, r, g, b, a):
+
+ if bo_cache is None:
+ byte_offset(src)
+
+ rv = [ a, a, a, a ]
+
+ for i, index_i in zip((r, g, b, a), bo_cache):
+ rv[index_i] = i
+
+ return rv
+
+
+
+def linmap(src, dst, rmap, gmap, bmap, amap):
+ """
+ This maps the colors between two surfaces. The various map
+ parameters should be fixed-point integers, with 1.0 == 256.
+ """
+
+ convert_and_call(_renpy.linmap,
+ src, dst,
+ *endian_order(dst, rmap, gmap, bmap, amap))
+
+
+save_png = _renpy.save_png
+
+def map(src, dst, rmap, gmap, bmap, amap): #@ReservedAssignment
+ """
+ This maps the colors between two surfaces. The various map
+ parameters must be 256 character long strings, with the value
+ of a character at a given offset being what a particular pixel
+ component value is mapped to.
+ """
+
+ convert_and_call(_renpy.map,
+ src, dst,
+ *endian_order(dst, rmap, gmap, bmap, amap))
+
+
+
+def twomap(src, dst, white, black):
+ """
+ Given colors for white and black, linearly maps things
+ appropriately, taking the alpha channel from white.
+ """
+
+ wr = white[0]
+ wg = white[1]
+ wb = white[2]
+ wa = white[3]
+
+ br = black[0]
+ bg = black[1]
+ bb = black[2]
+
+ ramp = renpy.display.im.ramp
+
+ if br == 0 and bg == 0 and bb == 0:
+ linmap(src, dst,
+ wr + 1,
+ wg + 1,
+ wb + 1,
+ wa + 1)
+ else:
+ list(map(src, dst,
+ ramp(br, wr),
+ ramp(bg, wg),
+ ramp(bb, wb),
+ ramp(0, wa)))
+
+
+def alpha_munge(src, dst, amap):
+ """
+ This samples the red channel from src, maps it through amap, and
+ place it into the alpha channel of amap.
+ """
+
+ if src.get_size() != dst.get_size():
+ return
+
+ red = byte_offset(src)[0]
+ alpha = byte_offset(dst)[3]
+
+ if red is not None and alpha is not None:
+ _renpy.alpha_munge(src, dst, red, alpha, amap)
+
+
+def bilinear_scale(src, dst, sx=0, sy=0, sw=None, sh=None, dx=0, dy=0, dw=None, dh=None, precise=0):
+
+ if sw is None:
+ sw, sh = src.get_size()
+ if dw is None:
+ dw, dh = dst.get_size()
+
+ while True:
+
+ if sw <= dw * 2 and sh <= dh * 2:
+ break
+
+ nsw = max(sw / 2, dw)
+ nsh = max(sh / 2, dh)
+
+ nsrc = renpy.display.pgrender.surface((nsw, nsh), src.get_masks()[3])
+
+ _renpy.bilinear(src, nsrc, sx, sy, sw, sh, precise=precise)
+
+ sx = 0
+ sy = 0
+ sw = nsw
+ sh = nsh
+ src = nsrc
+
+ _renpy.bilinear(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, precise=precise)
+
+
+transform = _renpy.transform
+
+# Note: Blend requires all surfaces to be the same size.
+blend = _renpy.blend
+
+def imageblend(a, b, dst, img, amap):
+ alpha = byte_offset(img)[3]
+ _renpy.imageblend(a, b, dst, img, alpha, amap)
+
+
+def colormatrix(src, dst, matrix):
+ c = [ matrix[0:5], matrix[5:10], matrix[10:15], matrix[15:20] ]
+ offs = byte_offset(src)
+
+ o = [ None ] * 4
+ for i in range(0, 4):
+ o[offs[i]] = i
+
+ _renpy.colormatrix(src, dst,
+ c[o[0]][o[0]], c[o[0]][o[1]], c[o[0]][o[2]], c[o[0]][o[3]], c[o[0]][4],
+ c[o[1]][o[0]], c[o[1]][o[1]], c[o[1]][o[2]], c[o[1]][o[3]], c[o[1]][4],
+ c[o[2]][o[0]], c[o[2]][o[1]], c[o[2]][o[2]], c[o[2]][o[3]], c[o[2]][4],
+ c[o[3]][o[0]], c[o[3]][o[1]], c[o[3]][o[2]], c[o[3]][o[3]], c[o[3]][4])
+
+
+def subpixel(src, dst, x, y):
+
+ shift = src.get_shifts()[3]
+ _renpy.subpixel(src, dst, x, y, shift)
+
+
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
diff --git a/unrpyc/renpy/display/movetransition.py b/unrpyc/renpy/display/movetransition.py
new file mode 100644
index 0000000..1b3bdd7
--- /dev/null
+++ b/unrpyc/renpy/display/movetransition.py
@@ -0,0 +1,640 @@
+# 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
+
+# Utility function used by MoveTransition et al.
+def position(d):
+
+ xpos, ypos, xanchor, yanchor, _xoffset, _yoffset, _subpixel = d.get_placement()
+
+ if xpos is None:
+ xpos = 0
+ if ypos is None:
+ ypos = 0
+ if xanchor is None:
+ xanchor = 0
+ if yanchor is None:
+ yanchor = 0
+
+ return xpos, ypos, xanchor, yanchor
+
+def offsets(d):
+
+ _xpos, _ypos, _xanchor, _yanchor, xoffset, yoffset, _subpixel = d.get_placement()
+
+ if renpy.config.movetransition_respects_offsets:
+ return { 'xoffset' : xoffset, 'yoffset' : yoffset }
+ else:
+ return { }
+
+
+# These are used by MoveTransition.
+def MoveFactory(pos1, pos2, delay, d, **kwargs):
+ if pos1 == pos2:
+ return d
+
+ return renpy.display.motion.Move(pos1, pos2, delay, d, **kwargs)
+
+def default_enter_factory(pos, delay, d, **kwargs):
+ return d
+
+def default_leave_factory(pos, delay, d, **kwargs):
+ return None
+
+# These can be used to move things in and out of the screen.
+def MoveIn(pos, pos1, delay, d, **kwargs):
+
+ def aorb(a, b):
+ if a is None:
+ return b
+ return a
+
+ pos = tuple([aorb(a, b) for a, b in zip(pos, pos1)])
+ return renpy.display.motion.Move(pos, pos1, delay, d, **kwargs)
+
+def MoveOut(pos, pos1, delay, d, **kwargs):
+
+ def aorb(a, b):
+ if a is None:
+ return b
+ return a
+
+ pos = tuple([aorb(a, b) for a, b in zip(pos, pos1)])
+ return renpy.display.motion.Move(pos1, pos, delay, d, **kwargs)
+
+def ZoomInOut(start, end, pos, delay, d, **kwargs):
+
+ xpos, ypos, xanchor, yanchor = pos
+
+ FactorZoom = renpy.display.motion.FactorZoom
+
+ if end == 1.0:
+ return FactorZoom(start, end, delay, d, after_child=d, opaque=False,
+ xpos=xpos, ypos=ypos, xanchor=xanchor, yanchor=yanchor, **kwargs)
+ else:
+ return FactorZoom(start, end, delay, d, opaque=False,
+ xpos=xpos, ypos=ypos, xanchor=xanchor, yanchor=yanchor, **kwargs)
+
+def RevolveInOut(start, end, pos, delay, d, **kwargs):
+ return renpy.display.motion.Revolve(start, end, delay, d, pos=pos, **kwargs)
+
+
+def OldMoveTransition(delay, old_widget=None, new_widget=None, factory=None, enter_factory=None, leave_factory=None, old=False, layers=[ 'master' ]):
+ """
+ Returns a transition that attempts to find images that have changed
+ position, and moves them from the old position to the new transition, taking
+ delay seconds to complete the move.
+
+ If `factory` is given, it is expected to be a function that takes as
+ arguments: an old position, a new position, the delay, and a
+ displayable, and to return a displayable as an argument. If not
+ given, the default behavior is to move the displayable from the
+ starting to the ending positions. Positions are always given as
+ (xpos, ypos, xanchor, yanchor) tuples.
+
+ If `enter_factory` or `leave_factory` are given, they are expected
+ to be functions that take as arguments a position, a delay, and a
+ displayable, and return a displayable. They are applied to
+ displayables that are entering or leaving the scene,
+ respectively. The default is to show in place displayables that
+ are entering, and not to show those that are leaving.
+
+ If `old` is True, then factory moves the old displayable with the
+ given tag. Otherwise, it moves the new displayable with that
+ tag.
+
+ `layers` is a list of layers that the transition will be applied
+ to.
+
+ Images are considered to be the same if they have the same tag, in
+ the same way that the tag is used to determine which image to
+ replace or to hide. They are also considered to be the same if
+ they have no tag, but use the same displayable.
+
+ Computing the order in which images are displayed is a three-step
+ process. The first step is to create a list of images that
+ preserves the relative ordering of entering and moving images. The
+ second step is to insert the leaving images such that each leaving
+ image is at the lowest position that is still above all images
+ that were below it in the original scene. Finally, the list
+ is sorted by zorder, to ensure no zorder violations occur.
+
+ If you use this transition to slide an image off the side of the
+ screen, remember to hide it when you are done. (Or just use
+ a leave_factory.)
+ """
+
+ if factory is None:
+ factory = MoveFactory
+
+ if enter_factory is None:
+ enter_factory = default_enter_factory
+
+ if leave_factory is None:
+ leave_factory = default_leave_factory
+
+ use_old = old
+
+ def merge_slide(old, new):
+
+ # If new does not have .layers or .scene_list, then we simply
+ # insert a move from the old position to the new position, if
+ # a move occured.
+
+ if (not isinstance(new, renpy.display.layout.MultiBox)
+ or (new.layers is None and new.layer_name is None)):
+
+ if use_old:
+ child = old
+ else:
+ child = new
+
+ old_pos = position(old)
+ new_pos = position(new)
+
+ if old_pos != new_pos:
+ return factory(old_pos,
+ new_pos,
+ delay,
+ child,
+ **offsets(child)
+ )
+
+ else:
+ return child
+
+ # If we're in the layers_root widget, merge the child widgets
+ # for each layer.
+ if new.layers:
+
+ assert old.layers
+
+ rv = renpy.display.layout.MultiBox(layout='fixed')
+ rv.layers = { }
+
+ for layer in renpy.config.layers:
+
+ f = new.layers[layer]
+
+ if (isinstance(f, renpy.display.layout.MultiBox)
+ and layer in layers
+ and f.scene_list is not None):
+
+ f = merge_slide(old.layers[layer], new.layers[layer])
+
+ rv.layers[layer] = f
+ rv.add(f)
+
+ return rv
+
+ # Otherwise, we recompute the scene list for the two widgets, merging
+ # as appropriate.
+
+ # Wraps the displayable found in SLE so that the various timebases
+ # are maintained.
+ def wrap(sle):
+ return renpy.display.layout.AdjustTimes(sle.displayable, sle.show_time, sle.animation_time)
+
+ def tag(sle):
+ return sle.tag or sle.displayable
+
+ def merge(sle, d):
+ rv = sle.copy()
+ rv.show_time = 0
+ rv.displayable = d
+ return rv
+
+ def entering(sle):
+ new_d = wrap(new_sle)
+ move = enter_factory(position(new_d), delay, new_d, **offsets(new_d))
+
+ if move is None:
+ return
+
+ rv_sl.append(merge(new_sle, move))
+
+ def leaving(sle):
+ old_d = wrap(sle)
+ move = leave_factory(position(old_d), delay, old_d, **offsets(old_d))
+
+ if move is None:
+ return
+
+ move = renpy.display.layout.IgnoresEvents(move)
+ rv_sl.append(merge(old_sle, move))
+
+
+ def moving(old_sle, new_sle):
+ old_d = wrap(old_sle)
+ new_d = wrap(new_sle)
+
+ if use_old:
+ child = old_d
+ else:
+ child = new_d
+
+ move = factory(position(old_d), position(new_d), delay, child, **offsets(child))
+ if move is None:
+ return
+
+ rv_sl.append(merge(new_sle, move))
+
+
+ # The old, new, and merged scene_lists.
+ old_sl = old.scene_list[:]
+ new_sl = new.scene_list[:]
+ rv_sl = [ ]
+
+
+ # A list of tags in old_sl, new_sl, and rv_sl.
+ old_map = dict((tag(i), i) for i in old_sl if i is not None)
+ new_tags = set(tag(i) for i in new_sl if i is not None)
+ rv_tags = set()
+
+ while old_sl or new_sl:
+
+ # If we have something in old_sl, then
+ if old_sl:
+
+ old_sle = old_sl[0]
+ old_tag = tag(old_sle)
+
+ # If the old thing has already moved, then remove it.
+ if old_tag in rv_tags:
+ old_sl.pop(0)
+ continue
+
+ # If the old thing does not match anything in new_tags,
+ # have it enter.
+ if old_tag not in new_tags:
+ leaving(old_sle)
+ rv_tags.add(old_tag)
+ old_sl.pop(0)
+ continue
+
+
+ # Otherwise, we must have something in new_sl. We want to
+ # either move it or have it enter.
+
+ new_sle = new_sl.pop(0)
+ new_tag = tag(new_sle)
+
+ # If it exists in both, move.
+ if new_tag in old_map:
+ old_sle = old_map[new_tag]
+
+ moving(old_sle, new_sle)
+ rv_tags.add(new_tag)
+ continue
+
+ else:
+ entering(new_sle)
+ rv_tags.add(new_tag)
+ continue
+
+ # Sort everything by zorder, to ensure that there are no zorder
+ # violations in the result.
+ rv_sl.sort(key=lambda a : a.zorder)
+
+ layer = new.layer_name
+ rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **renpy.game.interface.layer_properties[layer])
+ rv.append_scene_list(rv_sl)
+ rv.layer_name = layer
+
+ return rv
+
+
+ # This calls merge_slide to actually do the merging.
+
+ rv = merge_slide(old_widget, new_widget)
+ rv.delay = delay # W0201
+
+ return rv
+
+##############################################################################
+# New Move Transition (since 6.14)
+
+
+class MoveInterpolate(renpy.display.core.Displayable):
+ """
+ This displayable has two children. It interpolates between the positions
+ of its two children to place them on the screen.
+ """
+
+ def __init__(self, delay, old, new, use_old, time_warp):
+ super(MoveInterpolate, self).__init__()
+
+ # The old and new displayables.
+ self.old = old
+ self.new = new
+
+ # Should we display the old displayable?
+ self.use_old = False
+
+ # Time warp function or None.
+ self.time_warp = time_warp
+
+ # The width of the screen.
+ self.screen_width = 0
+ self.screen_height = 0
+
+ # The width of the selected child.
+ self.child_width = 0
+ self.child_height = 0
+
+ # The delay and st.
+ self.delay = delay
+ self.st = 0
+
+ def render(self, width, height, st, at):
+ self.screen_width = width
+ self.screen_height = height
+
+ old_r = renpy.display.render.render(self.old, width, height, st, at)
+ new_r = renpy.display.render.render(self.new, width, height, st, at)
+
+ if self.use_old:
+ cr = old_r
+ else:
+ cr = new_r
+
+ self.child_width, self.child_height = cr.get_size()
+ self.st = st
+
+ if self.st < self.delay:
+ renpy.display.render.redraw(self, 0)
+
+ return cr
+
+ def child_placement(self, child):
+
+ def based(v, base):
+ if v is None:
+ return 0
+ elif isinstance(v, int):
+ return v
+ elif isinstance(v, renpy.display.core.absolute):
+ return v
+ else:
+ return v * base
+
+ xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = child.get_placement()
+
+ xpos = based(xpos, self.screen_width)
+ ypos = based(ypos, self.screen_height)
+ xanchor = based(xanchor, self.child_width)
+ yanchor = based(yanchor, self.child_height)
+
+ return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+ def get_placement(self):
+
+ if self.st > self.delay:
+ done = 1.0
+ else:
+ done = self.st / self.delay
+
+ if self.time_warp is not None:
+ done = self.time_warp(done)
+
+ absolute = renpy.display.core.absolute
+
+ def I(a, b):
+ return absolute(a + done * (b - a))
+
+ old_xpos, old_ypos, old_xanchor, old_yanchor, old_xoffset, old_yoffset, old_subpixel = self.child_placement(self.old)
+ new_xpos, new_ypos, new_xanchor, new_yanchor, new_xoffset, new_yoffset, new_subpixel = self.child_placement(self.new)
+
+ xpos = I(old_xpos, new_xpos)
+ ypos = I(old_ypos, new_ypos)
+ xanchor = I(old_xanchor, new_xanchor)
+ yanchor = I(old_yanchor, new_yanchor)
+ xoffset = I(old_xoffset, new_xoffset)
+ yoffset = I(old_yoffset, new_yoffset)
+ subpixel = old_subpixel or new_subpixel
+
+ return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+
+def MoveTransition(delay, old_widget=None, new_widget=None, enter=None, leave=None, old=False, layers=[ 'master' ], time_warp=None, enter_time_warp=None, leave_time_warp=None):
+ """
+ :doc: transition function
+ :args: (delay, enter=None, leave=None, old=False, layers=['master'], time_warp=None, enter_time_warp=None, leave_time_warp=None)
+ :name: MoveTransition
+
+ Returns a transition that interpolates the position of images (with the
+ same tag) in the old and new scenes.
+
+ `delay`
+ The time it takes for the interpolation to finish.
+
+ `enter`
+ If not None, images entering the scene will also be moved. The value
+ of `enter` should be a transform that is applied to the image to
+ get its starting position.
+
+ `leave`
+ If not None, images leaving the scene will also be move. The value
+ of `leave` should be a transform that is applied to the image to
+ get its ending position.
+
+ `old`
+ If true, the old image will be used in preference to the new one.
+
+ `layers`
+ A list of layers that moves are applied to.
+
+ `time_warp`
+ A time warp function that's applied to the interpolation. This
+ takes a number between 0.0 and 1.0, and should return a number in
+ the same range.
+
+ `enter_time_warp`
+ A time warp function that's applied to images entering the scene.
+
+ `enter_time_warp`
+ A time warp function that's applied to images leaving the scene.
+
+ """
+
+ use_old = old
+
+ def merge_slide(old, new):
+
+ # If new does not have .layers or .scene_list, then we simply
+ # insert a move from the old position to the new position, if
+ # a move occured.
+
+ if (not isinstance(new, renpy.display.layout.MultiBox)
+ or (new.layers is None and new.layer_name is None)):
+
+ if old is new:
+ return new
+ else:
+ return MoveInterpolate(delay, old, new, use_old, time_warp)
+
+
+ # If we're in the layers_root widget, merge the child widgets
+ # for each layer.
+ if new.layers:
+
+ assert old.layers
+
+ rv = renpy.display.layout.MultiBox(layout='fixed')
+
+ for layer in renpy.config.layers:
+
+ f = new.layers[layer]
+
+ if (isinstance(f, renpy.display.layout.MultiBox)
+ and layer in layers
+ and f.scene_list is not None):
+
+ f = merge_slide(old.layers[layer], new.layers[layer])
+
+ rv.add(f)
+
+ return rv
+
+ # Otherwise, we recompute the scene list for the two widgets, merging
+ # as appropriate.
+
+ # Wraps the displayable found in SLE so that the various timebases
+ # are maintained.
+ def wrap(sle):
+ return renpy.display.layout.AdjustTimes(sle.displayable, sle.show_time, sle.animation_time)
+
+ def tag(sle):
+ return sle.tag or sle.displayable
+
+ def merge(sle, d):
+ rv = sle.copy()
+ rv.show_time = 0
+ rv.displayable = d
+ return rv
+
+ def entering(sle):
+
+ if not enter:
+ return
+
+ new_d = wrap(new_sle)
+ move = MoveInterpolate(delay, enter(new_d), new_d, False, enter_time_warp)
+ rv_sl.append(merge(new_sle, move))
+
+ def leaving(sle):
+
+ if not leave:
+ return
+
+ old_d = wrap(sle)
+ move = MoveInterpolate(delay, old_d, leave(old_d), True, leave_time_warp)
+ move = renpy.display.layout.IgnoresEvents(move)
+ rv_sl.append(merge(old_sle, move))
+
+
+ def moving(old_sle, new_sle):
+
+ if old_sle.displayable is new_sle.displayable:
+ rv_sl.append(new_sle)
+ return
+
+ old_d = wrap(old_sle)
+ new_d = wrap(new_sle)
+
+ move = MoveInterpolate(delay, old_d, new_d, use_old, time_warp)
+
+ rv_sl.append(merge(new_sle, move))
+
+
+ # The old, new, and merged scene_lists.
+ old_sl = old.scene_list[:]
+ new_sl = new.scene_list[:]
+ rv_sl = [ ]
+
+ # A list of tags in old_sl, new_sl, and rv_sl.
+ old_map = dict((tag(i), i) for i in old_sl if i is not None)
+ new_tags = set(tag(i) for i in new_sl if i is not None)
+ rv_tags = set()
+
+ while old_sl or new_sl:
+
+ # If we have something in old_sl, then
+ if old_sl:
+
+ old_sle = old_sl[0]
+ old_tag = tag(old_sle)
+
+ # If the old thing has already moved, then remove it.
+ if old_tag in rv_tags:
+ old_sl.pop(0)
+ continue
+
+ # If the old thing does not match anything in new_tags,
+ # have it enter.
+ if old_tag not in new_tags:
+ leaving(old_sle)
+ rv_tags.add(old_tag)
+ old_sl.pop(0)
+ continue
+
+
+ # Otherwise, we must have something in new_sl. We want to
+ # either move it or have it enter.
+
+ new_sle = new_sl.pop(0)
+ new_tag = tag(new_sle)
+
+ # If it exists in both, move.
+ if new_tag in old_map:
+ old_sle = old_map[new_tag]
+
+ moving(old_sle, new_sle)
+ rv_tags.add(new_tag)
+ continue
+
+ else:
+ entering(new_sle)
+ rv_tags.add(new_tag)
+ continue
+
+ # Sort everything by zorder, to ensure that there are no zorder
+ # violations in the result.
+ rv_sl.sort(key=lambda a : a.zorder)
+
+ layer = new.layer_name
+ rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **renpy.game.interface.layer_properties[layer])
+ rv.append_scene_list(rv_sl)
+
+ return rv
+
+ # Call merge_slide to actually do the merging.
+ rv = merge_slide(old_widget, new_widget)
+ rv.delay = delay
+
+ return rv
+
diff --git a/unrpyc/renpy/display/particle.py b/unrpyc/renpy/display/particle.py
new file mode 100644
index 0000000..cf52417
--- /dev/null
+++ b/unrpyc/renpy/display/particle.py
@@ -0,0 +1,615 @@
+# 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 code supports sprite and particle animation.
+
+from renpy.display.render import render, BLIT
+
+import renpy.display
+import random
+
+
+class SpriteCache(renpy.object.Object):
+ """
+ This stores information about a displayble, including the identity
+ of the displayable, and when it was first displayed. It is also
+ responsible for caching the displayable surface, so it doesn't
+ need to be re-rendered.
+ """
+
+ # Private Fields:
+ #
+ # child - The child displayable.
+ #
+ # st - The shown time when this was first displayed, or None if it hasn't
+ # been rendered.
+ #
+ # render - The render of child.
+ #
+ # If true, then the render is simple enough it can just be appended to
+ # the manager's render's children list.
+
+class Sprite(renpy.object.Object):
+ """
+ :doc: sprites class
+
+ This represents a sprite that is managed by the SpriteManager. It contains
+ fields that control the placement of the sprite on the screen. Sprites
+ should not be created directly. Instead, they should be created by
+ calling :meth:`SpriteManager.create`.
+
+ The fields of a sprite object are:
+
+ `x`, `y`
+ The x and y coordinates of the upper-left corner of the sprite,
+ relative to the SpriteManager.
+
+ `zorder`
+ An integer that's used to control the order of this sprite in the
+ relative to the other sprites in the SpriteManager. The larger the
+ number is, the closer to the viewer the sprite is.
+
+ `events`
+ If True, then events are passed to child. If False, the default,
+ the children igore events (and hence don't spend time processing
+ them).
+
+ The methods of a Sprite object are:
+ """
+
+ # Fields:
+ #
+ # child - the displayable that is the child of this sprite.
+ # cache - the SpriteCache of child.
+ # live - True if this sprite is still alive.
+ # manager - A reference to the SpriteManager.
+
+ def set_child(self, d):
+ """
+ :doc: sprites method
+
+ Changes the Displayable associated with this sprite to `d`.
+ """
+
+ id_d = id(d)
+
+ sc = self.manager.displayable_map.get(id_d, None)
+ if sc is None:
+ d = renpy.easy.displayable(d)
+
+ sc = SpriteCache()
+ sc.render = None
+ sc.child = d
+ sc.st = None
+
+ self.manager.displayable_map[id_d] = sc
+
+ self.cache = sc
+
+ def destroy(self):
+ """
+ :doc: sprites method
+
+ Destroys this sprite, preventing it from being displayed and
+ removing it from the SpriteManager.
+ """
+
+ self.manager.dead_child = True
+ self.live = False
+ self.events = False
+
+
+
+class SpriteManager(renpy.display.core.Displayable):
+ """
+ :doc: sprites class
+
+ This displayable manages a collection of sprites, and displays
+ them at the fastest speed possible.
+ """
+
+ def __init__(self, update=None, event=None, predict=None, ignore_time=False, **properties):
+ """
+ `update`
+ If not None, a function that is called each time a sprite
+ is rendered by this sprite manager. It is called with one
+ argument, the time in seconds since this sprite manager
+ was first displayed. It is expected to return the number
+ of seconds until the function is called again, and the
+ SpriteManager is rendered again.
+
+ `event`
+ If not None, a function that is called when an event occurs.
+ It takes as arguments:
+ * A pygame event object.
+ * The x coordinate of the event.
+ * The y coordinate of the event.
+ * The time since the sprite manager was first shown.
+ If it returns a non-None value, the interaction ends, and
+ that value is returned.
+
+ `predict`
+ If not None, a function that returns a list of
+ displayables. These displayables are predicted when the
+ sprite manager is.
+
+ `ignore_time`
+ If True, then time is ignored when rendering displayables. This
+ should be used when the sprite manager is used with a relatively
+ small pool of images, and those images do not change over time.
+ This should only be used with a small number of displayables, as
+ it will keep all displayables used in memory for the life of the
+ SpriteManager.
+
+ After being rendered once (before the `update` function is called),
+ SpriteManagers have the following fields:
+
+ `width`, `height`
+
+ The width and height of this SpriteManager, in pixels.
+
+
+ SpriteManagers have the following methods:
+ """
+
+ super(SpriteManager, self).__init__(self, **properties)
+
+ self.update_function = update
+ self.event_function = event
+ self.predict_function = predict
+ self.ignore_time = ignore_time
+
+ # A map from a displayable to the SpriteDisplayable object
+ # representing that displayable.
+ self.displayable_map = { }
+
+ # A list of children of this displayable, in zorder. (When sorted.)
+ # This is a list of Sprites.
+ self.children = [ ]
+
+ # True if at least one child has been killed.
+ self.dead_child = False
+
+ # True if at least one child responds to events.
+ self.events = False
+
+ # The width and height.
+ self.width = None
+ self.height = None
+
+ def create(self, d):
+ """
+ :doc: sprites method
+
+ Creates a new Sprite for the displayable `d`, and adds it to this
+ SpriteManager.
+ """
+
+ id_d = id(d)
+
+ sc = self.displayable_map.get(id_d, None)
+ if sc is None:
+ d = renpy.easy.displayable(d)
+
+ sc = SpriteCache()
+ sc.render = None
+ sc.child = d
+ sc.st = None
+ self.displayable_map[id_d] = sc
+
+ s = Sprite()
+ s.x = 0
+ s.y = 0
+ s.zorder = 0
+ s.cache = sc
+ s.live = True
+ s.manager = self
+ s.events = False
+
+ self.children.append(s)
+
+ return s
+
+ def predict_one(self):
+ if self.predict_function is not None:
+ for i in self.predict_function():
+ renpy.display.predict.displayable(i)
+
+
+ def redraw(self, delay=0):
+ """
+ :doc: sprite method
+
+ Causes this SpriteManager to be redrawn in `delay` seconds.
+ """
+
+ renpy.display.render.redraw(self, delay)
+
+ def render(self, width, height, st, at):
+
+ self.width = width
+ self.height = height
+
+ if self.update_function is not None:
+
+ redraw = self.update_function(st)
+
+ if redraw is not None:
+ renpy.display.render.redraw(self, redraw)
+
+ if not self.ignore_time:
+ self.displayable_map.clear()
+
+ if self.dead_child:
+ self.children = [ i for i in self.children if i.live ]
+
+ self.children.sort(key=lambda sc:sc.zorder)
+
+ caches = [ ]
+
+ rv = renpy.display.render.Render(width, height)
+
+ events = False
+
+ for i in self.children:
+
+ events |= i.events
+
+ cache = i.cache
+ r = i.cache.render
+ if cache.render is None:
+ if cache.st is None:
+ cache.st = st
+
+ cst = st - cache.st
+
+ cache.render = r = render(cache.child, width, height, cst, cst)
+ cache.fast = (r.operation == BLIT) and (r.forward is None) and (r.alpha == 1.0)
+ rv.depends_on(r)
+
+ caches.append(cache)
+
+
+ if cache.fast:
+ for child, xo, yo, _focus, _main in r.children:
+ rv.children.append((child,
+ xo + i.x,
+ yo + i.y,
+ False,
+ False))
+
+ else:
+ rv.subpixel_blit(r, (i.x, i.y))
+
+ for i in caches:
+ i.render = None
+
+ return rv
+
+ def event(self, ev, x, y, st):
+ for i in range(len(self.children) -1, -1, -1):
+ s = self.children[i]
+
+ if s.events:
+ rv = s.cache.child.event(ev, x - s.x, y - s.y, st - s.cache.st)
+ if rv is not None:
+ return rv
+
+ if self.event_function is not None:
+ return self.event_function(ev, x, y, st)
+ else:
+ return None
+
+ def visit(self):
+ rv = [ ]
+
+ try:
+ if self.predict_function:
+ pl = self.predict_function()
+ for i in pl:
+ i = renpy.easy.displayable(i)
+ rv.append(i)
+ except:
+ pass
+
+ return rv
+
+ def destroy_all(self):
+ self.children = [ ]
+
+
+class Particles(renpy.display.core.Displayable, renpy.python.NoRollback):
+ """
+ Supports particle motion, using the old API.
+ """
+
+ __version__ = 1
+
+ nosave = [ 'particles' ]
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+
+ def after_setstate(self):
+ self.particles = None
+
+ def __init__(self, factory, **properties):
+ """
+ @param factory: A factory object.
+ """
+
+ super(Particles, self).__init__(**properties)
+
+ self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+
+ self.factory = factory
+ self.particles = None
+
+ def update_callback(self, st):
+
+ particles = self.particles
+
+ if st == 0 or particles is None:
+ self.sm.destroy_all()
+ particles = [ ]
+
+ add_parts = self.factory.create(particles, st)
+
+ new_particles = [ ]
+
+ for sprite, p in particles:
+ update = p.update(st)
+
+ if update is None:
+ sprite.destroy()
+ continue
+
+ x, y, _t, d = update
+
+ if d is not sprite.cache.child:
+ sprite.set_child(d)
+
+ sprite.x = x
+ sprite.y = y
+
+ new_particles.append((sprite, p))
+
+ if add_parts:
+ for p in add_parts:
+ update = p.update(st)
+
+ if update is None:
+ continue
+
+ x, y, _t, d = update
+
+ if d is None:
+ continue
+
+ sprite = self.sm.create(d)
+ sprite.x = x
+ sprite.y = y
+
+ new_particles.append((sprite, p))
+
+ self.particles = new_particles
+
+ return 0
+
+ def predict_callback(self):
+ return self.factory.predict()
+
+ def render(self, w, h, st, at):
+ return renpy.display.render.render(self.sm, w, h, st, at)
+
+class SnowBlossomFactory(renpy.python.NoRollback):
+
+ rotate = False
+
+ def __setstate__(self, state):
+ self.start = 0
+ vars(self).update(state)
+ self.init()
+
+ def __init__(self, image, count, xspeed, yspeed, border, start, fast, rotate=False):
+ self.image = renpy.easy.displayable(image)
+ self.count = count
+ self.xspeed = xspeed
+ self.yspeed = yspeed
+ self.border = border
+ self.start = start
+ self.fast = fast
+ self.rotate = rotate
+ self.init()
+
+ def init(self):
+ self.starts = [ random.uniform(0, self.start) for _i in range(0, self.count) ] # W0201
+ self.starts.append(self.start)
+ self.starts.sort()
+
+ def create(self, particles, st):
+
+ def ranged(n):
+ if isinstance(n, tuple):
+ return random.uniform(n[0], n[1])
+ else:
+ return n
+
+ if not particles and self.fast:
+ rv = [ ]
+
+ for _i in range(0, self.count):
+ rv.append(SnowBlossomParticle(self.image,
+ ranged(self.xspeed),
+ ranged(self.yspeed),
+ self.border,
+ st,
+ random.uniform(0, 100),
+ fast=True,
+ rotate=self.rotate))
+ return rv
+
+
+ if particles is None or len(particles) < self.count:
+
+ # Check to see if we have a particle ready to start. If not,
+ # don't start it.
+ if particles and st < self.starts[len(particles)]:
+ return None
+
+ return [ SnowBlossomParticle(self.image,
+ ranged(self.xspeed),
+ ranged(self.yspeed),
+ self.border,
+ st,
+ random.uniform(0, 100),
+ fast=False,
+ rotate=self.rotate) ]
+
+ def predict(self):
+ return [ self.image ]
+
+
+class SnowBlossomParticle(renpy.python.NoRollback):
+
+ def __init__(self, image, xspeed, yspeed, border, start, offset, fast, rotate):
+
+ # safety.
+ if yspeed == 0:
+ yspeed = 1
+
+ self.image = image
+ self.xspeed = xspeed
+ self.yspeed = yspeed
+ self.border = border
+ self.start = start
+ self.offset = offset
+ self.rotate = rotate
+
+
+ if not rotate:
+ sh = renpy.config.screen_height
+ sw = renpy.config.screen_width
+ else:
+ sw = renpy.config.screen_height
+ sh = renpy.config.screen_width
+
+
+ if self.yspeed > 0:
+ self.ystart = -border
+ else:
+ self.ystart = sh + border
+
+
+ travel_time = (2.0 * border + sh) / abs(yspeed)
+
+ xdist = xspeed * travel_time
+
+ x0 = min(-xdist, 0)
+ x1 = max(sw + xdist, sw)
+
+ self.xstart = random.uniform(x0, x1)
+
+ if fast:
+ self.ystart = random.uniform(-border, sh + border)
+ self.xstart = random.uniform(0, sw)
+
+ def update(self, st):
+ to = st - self.start
+
+ xpos = self.xstart + to * self.xspeed
+ ypos = self.ystart + to * self.yspeed
+
+ if not self.rotate:
+ sh = renpy.config.screen_height
+ else:
+ sh = renpy.config.screen_width
+
+ if ypos > sh + self.border:
+ return None
+
+ if ypos < -self.border:
+ return None
+
+ if not self.rotate:
+ return int(xpos), int(ypos), to + self.offset, self.image
+ else:
+ return int(ypos), int(xpos), to + self.offset, self.image
+
+def SnowBlossom(d,
+ count=10,
+ border=50,
+ xspeed=(20, 50),
+ yspeed=(100, 200),
+ start=0,
+ fast=False,
+ horizontal=False):
+
+ """
+ :doc: sprites_extra
+
+ The snowblossom effect moves multiple instances of a sprite up,
+ down, left or right on the screen. When a sprite leaves the screen, it
+ is returned to the start.
+
+ `d`
+ The displayable to use for the sprites.
+
+ `border`
+ The size of the border of the screen. The sprite is considered to be
+ on the screen until it clears the border, ensuring that sprites do
+ not disappear abruptly.
+
+ `xspeed`, `yspeed`
+ The speed at which the sprites move, in the horizontal and vertical
+ directions, respectively. These can be a single number or a tuple of
+ two numbers. In the latter case, each particle is assigned a random
+ speed between the two numbers. The speeds can be positive or negative,
+ as long as the second number in a tuple is larger than the first.
+
+ `start`
+ The delay, in seconds, before each particle is added. This can be
+ allows the particles to start at the top of the screen, while not
+ looking like a "wave" effect.
+
+ `fast`
+ If true, particles start in the center of the screen, rather than
+ only at the edges.
+
+ `horizontal`
+ If true, particles appear on the left or right side of the screen,
+ rather than the top or bottom.
+ """
+
+ # If going horizontal, swap the xspeed and the yspeed.
+ if horizontal:
+ xspeed, yspeed = yspeed, xspeed
+
+ return Particles(SnowBlossomFactory(image=d,
+ count=count,
+ border=border,
+ xspeed=xspeed,
+ yspeed=yspeed,
+ start=start,
+ fast=fast,
+ rotate=horizontal))
+
diff --git a/unrpyc/renpy/display/pgrender.py b/unrpyc/renpy/display/pgrender.py
new file mode 100644
index 0000000..32d1df8
--- /dev/null
+++ b/unrpyc/renpy/display/pgrender.py
@@ -0,0 +1,167 @@
+# 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 module wraps the pygame surface class (and associated functions). It
+# ensures that returned surfaces have a 2px border around them.
+
+import sys
+import pygame
+import renpy.display
+
+# Sample surfaces, with and without alpha.
+sample_alpha = None
+sample_noalpha = None
+
+def set_rgba_masks():
+ """
+ This rebuilds the sample surfaces, to ones that use the given
+ masks.
+ """
+
+ # Annoyingly, the value for the big mask seems to vary from
+ # platform to platform. So we read it out of a surface.
+
+ global sample_alpha
+ global sample_noalpha
+
+ # Create a sample surface.
+ s = pygame.Surface((10, 10), 0, 32)
+ sample_alpha = s.convert_alpha()
+
+ # Sort the components by absolute value.
+ masks = list(sample_alpha.get_masks())
+ masks.sort(key=lambda a : abs(a))
+
+ # Choose the masks.
+ if sys.byteorder == 'big':
+ masks = ( masks[3], masks[2], masks[1], masks[0] )
+ else:
+ masks = ( masks[0], masks[1], masks[2], masks[3] )
+
+ # Create the sample surface.
+ sample_alpha = pygame.Surface((10, 10), 0, 32, masks)
+ sample_noalpha = pygame.Surface((10, 10), 0, 32, masks[:3] + (0,))
+
+
+class Surface(pygame.Surface):
+ """
+ This allows us to wrap around pygame's surface, to change
+ its mode, as necessary.
+ """
+
+ opaque = False
+
+ def is_opaque(self):
+ return self.opaque
+
+ def convert_alpha(self, surface=None):
+ return copy_surface_unscaled(self, True)
+
+ def convert(self, surface=None):
+ return copy_surface(self, False)
+
+ def copy(self):
+ return copy_surface(self, self)
+
+ def subsurface(self, rect):
+ rv = pygame.Surface.subsurface(self, rect)
+ return rv
+
+def surface(xxx_todo_changeme, alpha):
+ """
+ Constructs a new surface. The allocated surface is actually a subsurface
+ of a surface that has a 2 pixel border in all directions.
+
+ `alpha` - True if the new surface should have an alpha channel.
+ """
+ (width, height) = xxx_todo_changeme
+ if isinstance(alpha, pygame.Surface):
+ alpha = alpha.get_masks()[3]
+
+ if alpha:
+ sample = sample_alpha
+ else:
+ sample = sample_noalpha
+
+ # We might not have initialized properly yet. This is enough
+ # to get us underway.
+ if sample is None:
+ sample = pygame.Surface((4, 4), pygame.SRCALPHA, 32)
+
+ surf = Surface((width + 4, height + 4), 0, sample)
+ return surf.subsurface((2, 2, width, height)) # E1101
+
+surface_unscaled = surface
+
+def copy_surface(surf, alpha=True):
+ """
+ Creates a copy of the surface.
+ """
+
+ rv = surface_unscaled(surf.get_size(), alpha)
+ renpy.display.accelerator.nogil_copy(surf, rv) # @UndefinedVariable
+ return rv
+
+copy_surface_unscaled = copy_surface
+
+
+# Wrapper around image loading.
+
+def load_image(f, filename):
+ surf = pygame.image.load(f, renpy.exports.fsencode(filename))
+ rv = copy_surface_unscaled(surf)
+ return rv
+
+load_image_unscaled = load_image
+
+
+# Wrapper around functions we use from pygame.surface.
+
+def flip(surf, horizontal, vertical):
+ surf = pygame.transform.flip(surf, horizontal, vertical)
+ return copy_surface_unscaled(surf)
+
+flip_unscaled = flip
+
+
+def rotozoom(surf, angle, zoom):
+
+ surf = pygame.transform.rotozoom(surf, angle, zoom)
+ return copy_surface_unscaled(surf)
+
+rotozoom_unscaled = rotozoom
+
+
+def transform_scale(surf, size):
+ surf = pygame.transform.scale(surf, size)
+ return copy_surface_unscaled(surf, surf)
+
+transform_scale_unscaled = transform_scale
+
+
+def transform_rotate(surf, angle):
+ surf = pygame.transform.rotate(surf, angle)
+ return copy_surface(surf)
+
+transform_rotate_unscaled = transform_rotate
+
+
+
diff --git a/unrpyc/renpy/display/predict.py b/unrpyc/renpy/display/predict.py
new file mode 100644
index 0000000..2bdc16f
--- /dev/null
+++ b/unrpyc/renpy/display/predict.py
@@ -0,0 +1,156 @@
+# 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 the routines that manage image prediction.
+
+import renpy.display
+
+# Called to indicate an image should be loaded or preloaded. This is
+# a function that takes an image manipulator, set by reset and predict,
+# and winds up bound to either im.cache.get or im.cache.preload_image
+image = None
+
+# The set of displayables we've predicted since reset was last called.
+predicted = set()
+
+# A flag that indicates if we're currently predicting.
+predicting = False
+
+# A list of (screen name, argument dict) tuples, giving the screens we'd
+# like to predict.
+screens = [ ]
+
+def displayable(d):
+ """
+ Called to predict that the displayable `d` will be shown.
+ """
+
+ if d is None:
+ return
+
+ if d not in predicted:
+ predicted.add(d)
+ d.visit_all(lambda i : i.predict_one())
+
+def screen(_screen_name, *args, **kwargs):
+ """
+ Called to predict that the named screen is about to be shown
+ with the given arguments.
+ """
+
+ screens.append((_screen_name, args, kwargs))
+
+
+def reset():
+ global image
+ image = renpy.display.im.cache.get
+ predicted.clear()
+ del screens[:]
+
+
+def prediction_coroutine(root_widget):
+ """
+ The image prediction co-routine. This predicts the images that can
+ be loaded in the near future, and passes them to the image cache's
+ preload_image method to be queued up for loading.
+
+ The .send should be called with True to do a expensive prediction,
+ and with False to either do an inexpensive prediction or no
+ prediction at all.
+
+ Returns True if there's more predicting to be done, or False
+ if there's no more predicting worth doing.
+ """
+
+ global predicting
+
+ # Wait to be told to start.
+ yield True
+
+ # Start the prediction thread (to clean out the cache).
+ renpy.display.im.cache.start_prediction()
+
+ # Set up the image prediction method.
+ global image
+ image = renpy.display.im.cache.preload_image
+
+ # Predict images that are going to be reached in the next few
+ # clicks.
+ predicting = True
+
+ for _i in renpy.game.context().predict():
+
+ predicting = False
+ yield True
+ predicting = True
+
+ # If there's a parent context, predict we'll be returning to it
+ # shortly. Otherwise, call the functions in
+ # config.predict_callbacks.
+
+ if len(renpy.game.contexts) >= 2:
+ sls = renpy.game.contexts[-2].scene_lists
+
+ for l in sls.layers.values():
+ for sle in l:
+ try:
+ displayable(sle.displayable)
+ except:
+ pass
+
+ else:
+ for i in renpy.config.predict_callbacks:
+ i()
+
+ predicting = False
+
+ while not (yield True):
+ continue
+
+ # Predict things (especially screens) that are reachable through
+ # an action.
+ predicting = True
+
+ try:
+ root_widget.visit_all(lambda i : i.predict_one_action())
+ except:
+ pass
+
+ predicting = False
+
+ # Predict the screens themselves.
+ for name, args, kwargs in screens:
+ while not (yield True):
+ continue
+
+ predicting = True
+
+ try:
+ renpy.display.screen.predict_screen(name, *args, **kwargs)
+ except:
+ if renpy.config.debug_image_cache:
+ renpy.display.ic_log.write("While predicting screen %s %r", name, kwargs)
+ renpy.display.ic_log.exception()
+
+ predicting = False
+
+ yield False
+
diff --git a/unrpyc/renpy/display/presplash.py b/unrpyc/renpy/display/presplash.py
new file mode 100644
index 0000000..54717fd
--- /dev/null
+++ b/unrpyc/renpy/display/presplash.py
@@ -0,0 +1,113 @@
+# 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.
+
+# Pre-splash code. The goal of this code is to try to get a pre-splash
+# screen up as soon as possible, to let the user know something is
+# going on.
+
+# The presplash process, if any.
+proc = None
+
+# Called from the main process. This determines if
+# we're even doing presplash, and if so what will be shown to the
+# user. If it decides to show something to the user, uses subprocess
+# to actually handle the showing.
+def start(basedir, gamedir):
+ import os.path
+
+ if "RENPY_LESS_UPDATES" in os.environ:
+ return
+
+ global proc
+
+ filenames = [ "/presplash.png", "/presplash.jpg" ]
+ for fn in filenames:
+ fn = gamedir + fn
+ if os.path.exists(fn):
+ break
+ else:
+ return
+
+ try:
+ import subprocess
+ import sys
+
+ cmd = [sys.executable, "-EOO", sys.argv[0], "show", "presplash", fn]
+
+ def fsencode(s):
+ if isinstance(s, str):
+ return s
+
+ return s.encode(sys.getfilesystemencoding() or "utf-8", "replace")
+
+ proc = subprocess.Popen([ fsencode(i) for i in cmd ], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+ except:
+ pass
+
+
+# Called just before we initialize the display for real, to
+# hide the splash, and terminate window centering.
+def end():
+
+ global proc
+
+ if not proc:
+ return
+
+ proc.stdin.close()
+ proc.wait()
+
+ proc = None
+
+# Called in the presplash process, to actually display the presplash.
+def show(fn):
+
+ import pygame.display
+ import pygame.constants
+ import sys
+ import os
+
+ os.environ['SDL_VIDEO_CENTERED'] = "1"
+
+ try:
+ import pygame.macosx
+ pygame.macosx.init() #@UndefinedVariable
+ except:
+ pass
+
+ try:
+ import pygame.macosx #@Reimport
+ pygame.macosx.Video_AutoInit()
+ except:
+ pass
+
+ pygame.display.init()
+
+ img = pygame.image.load(fn, fn)
+ screen = pygame.display.set_mode(img.get_size(), pygame.constants.NOFRAME)
+ screen.blit(img, (0, 0))
+ pygame.display.update()
+
+ sys.stdout.write("READY\r\n")
+ sys.stdout.flush()
+ sys.stdin.read()
+
+ sys.exit(0)
diff --git a/unrpyc/renpy/display/render.pxd b/unrpyc/renpy/display/render.pxd
new file mode 100644
index 0000000..a4d8d68
--- /dev/null
+++ b/unrpyc/renpy/display/render.pxd
@@ -0,0 +1,47 @@
+cdef class Matrix2D:
+ cdef public double xdx
+ cdef public double xdy
+ cdef public double ydx
+ cdef public double ydy
+
+ cpdef tuple transform(Matrix2D self, double x, double y)
+
+cdef class Render:
+
+ cdef public bint mark, cache_killed
+
+ cdef public float width, height
+ cdef public object layer_name
+
+ cdef public list children
+ cdef public set parents
+ cdef public list depends_on_list
+
+ cdef public int operation
+ cdef public double operation_complete
+ cdef public bint operation_alpha
+ cdef public object operation_parameter
+
+ cdef public Matrix2D forward, reverse
+ cdef public double alpha
+
+ cdef public list focuses
+ cdef public list pass_focuses
+ cdef public object draw_func
+ cdef public object render_of
+
+ cdef public bint opaque
+ cdef public list visible_children
+
+ cdef public bint clipping
+
+ cdef public object surface, alpha_surface, half_cache
+
+ cdef public bint modal
+
+ cpdef int blit(Render self, source, tuple pos, object focus=*, object main=*, object index=*)
+ cpdef int subpixel_blit(Render self, source, tuple pos, object focus=*, object main=*, object index=*)
+
+
+cpdef render(object d, object widtho, object heighto, double st, double at)
+
diff --git a/unrpyc/renpy/display/render.pyx b/unrpyc/renpy/display/render.pyx
new file mode 100644
index 0000000..5aa45a0
--- /dev/null
+++ b/unrpyc/renpy/display/render.pyx
@@ -0,0 +1,1174 @@
+#cython: profile=False
+# 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.
+
+import collections
+import pygame
+import threading
+import renpy
+import gc
+
+# We grab the blit lock each time it is necessary to blit
+# something. This allows call to the pygame.transform functions to
+# disable blitting, should it prove necessary.
+blit_lock = threading.Condition()
+
+# This is a dictionary containing all the renders that we know of. It's a
+# map from displayable to dictionaries containing the render of that
+# displayable.
+render_cache = collections.defaultdict(dict)
+
+# The queue of redraws. A list of (time, displayable) pairs.
+redraw_queue = [ ]
+
+# The render returned from render_screen.
+screen_render = None
+
+# A list of renders the system knows about, and thinks are still alive.
+cdef list live_renders
+live_renders = [ ]
+
+# A copy of renpy.display.interface.frame_time, for speed reasons.
+cdef double frame_time
+frame_time = 0
+
+def free_memory():
+ """
+ Frees memory used by the render system.
+ """
+
+ global screen_render
+ screen_render = None
+
+ mark_sweep()
+
+ render_cache.clear()
+
+ # This can hang onto a render.
+ renpy.display.interface.surftree = None
+
+
+def check_at_shutdown():
+ """
+ This is called at shutdown time to check that everything went okay.
+ The big thing it checks for is memory leaks.
+ """
+
+ if not renpy.config.developer:
+ return
+
+ free_memory()
+
+ gc.collect()
+ l = gc.get_objects()
+
+ count = 0
+ objects = gc.get_objects()
+
+ for i in objects:
+ if isinstance(i, Render):
+ count += 1
+
+ if count:
+ raise Exception("%d Renders are alive at shutdown. This is probably a memory leak bug in Ren'Py." % count)
+
+
+
+cpdef render(d, object widtho, object heighto, double st, double at):
+ """
+ :doc: udd_utility
+ :args: (d, width, height, st, at)
+
+ Causes a displayable to be rendered, and a renpy.Render object to
+ be returned.
+
+ `d`
+ The displayable to render.
+
+ `width`, `height`
+ The width and height available for the displayable to render into.
+
+ `st`, `at`
+ The shown and animation timebases.
+
+ Renders returned by this object may be cached, and should not be modified
+ once they have been retrieved.
+ """
+
+ cdef float width, height
+ cdef float orig_width, orig_height
+ cdef tuple orig_wh, wh
+ cdef dict render_cache_d
+ cdef Render rv
+
+ orig_wh = (widtho, heighto, frame_time-st, frame_time-at)
+
+ render_cache_d = render_cache[d]
+ rv = render_cache_d.get(orig_wh, None)
+
+ if rv is not None:
+ return rv
+
+ orig_width = width = widtho
+ orig_height = height = heighto
+
+ style = d.style
+ xmaximum = style.xmaximum
+ ymaximum = style.ymaximum
+
+ if xmaximum is not None:
+ if isinstance(xmaximum, float):
+ width = width * xmaximum
+ else:
+ width = min(xmaximum, width)
+
+ if ymaximum is not None:
+ if isinstance(ymaximum, float):
+ height = height * ymaximum
+ else:
+ height = min(ymaximum, height)
+
+ if width < 0:
+ width = 0
+ if height < 0:
+ height = 0
+
+ if orig_width != width or orig_height != height:
+ widtho = width
+ heighto = height
+ wh = (widtho, heighto, frame_time-st, frame_time-at)
+ rv = render_cache_d.get(wh, None)
+
+ if rv is not None:
+ return rv
+
+ else:
+ wh = orig_wh
+
+ rv = d.render(widtho, heighto, st, at)
+
+ rv.render_of.append(d)
+
+ if style.clipping:
+ rv = rv.subsurface((0, 0, rv.width, rv.height), focus=True)
+ rv.render_of.append(d)
+
+ render_cache_d[wh] = rv
+
+ if wh is not orig_wh:
+ render_cache_d[orig_wh] = rv
+
+ return rv
+
+
+# This is true if something has been invalidated, and a redraw needs
+# to occur. It's automatically cleared to False at the end of each
+# redraw.
+invalidated = False
+
+def invalidate(d):
+ """
+ Removes d from the render cache. If we're not in a redraw, triggers
+ a redraw to start.
+ """
+
+ global invalidated
+
+ if d in render_cache:
+ for v in render_cache[d].values():
+ v.kill_cache()
+
+ invalidated = True
+
+
+def process_redraws():
+ """
+ Called to determine if any redraws are pending. Returns true if we
+ need to redraw the screen now, false otherwise.
+ """
+
+ global redraw_queue
+
+ redraw_queue.sort()
+
+ now = renpy.display.core.get_time()
+ rv = invalidated
+
+ new_redraw_queue = [ ]
+ seen = set()
+
+ for t in redraw_queue:
+ when, d = t
+
+ if d in seen:
+ continue
+
+ seen.add(d)
+
+ if d not in render_cache:
+ continue
+
+ if when <= now:
+ # Remove this displayable and all its parents from the
+ # render cache. But don't kill them yet, as that will kill the
+ # children that we want to reuse.
+
+ for v in render_cache[d].values():
+ v.kill_cache()
+
+ rv = True
+
+ else:
+ new_redraw_queue.append(t)
+
+ redraw_queue = new_redraw_queue
+
+ return rv
+
+
+def redraw_time():
+ """
+ Returns the time at which the next redraw is scheduled.
+ """
+
+ if redraw_queue:
+ return redraw_queue[0][0]
+
+ return None
+
+
+def redraw(d, when):
+ """
+ :doc: udd_utility
+
+ Causes the displayable `d` to be redrawn after `when` seconds have
+ elapsed.
+ """
+
+ if not renpy.game.interface:
+ return
+
+ redraw_queue.append((when + renpy.game.interface.frame_time, d))
+
+
+cdef class Matrix2D:
+ """
+ This represents a 2d matrix that can be used to transform
+ points and things like that.
+ """
+
+ def __getstate__(self):
+ return dict(
+ xdx = self.xdx,
+ xdy = self.xdy,
+ ydx = self.ydx,
+ ydy = self.ydy)
+
+ def __setstate__(self, state):
+ self.xdx = state['xdx']
+ self.xdy = state['xdy']
+ self.ydx = state['ydx']
+ self.ydy = state['ydy']
+
+ def __init__(Matrix2D self, double xdx, double xdy, double ydx, double ydy):
+ self.xdx = xdx
+ self.xdy = xdy
+ self.ydx = ydx
+ self.ydy = ydy
+
+ cpdef tuple transform(Matrix2D self, double x, double y):
+ return (x * self.xdx + y * self.xdy), (x * self.ydx + y * self.ydy)
+
+ def __mul__(Matrix2D self, Matrix2D other):
+ return Matrix2D(
+ other.xdx * self.xdx + other.xdy * self.ydx,
+ other.xdx * self.xdy + other.xdy * self.ydy,
+ other.ydx * self.xdx + other.ydy * self.ydx,
+ other.ydx * self.xdy + other.ydy * self.ydy)
+
+ def __repr__(self):
+ return "Matrix2D(xdx=%f, xdy=%f, ydx=%f, ydy=%f)" % (self.xdx, self.xdy, self.ydx, self.ydy)
+
+IDENTITY = Matrix2D(1, 0, 0, 1)
+
+def take_focuses(focuses):
+ """
+ Adds a list of rectangular focus regions to the focuses list.
+ """
+
+ screen_render.take_focuses(
+ 0, 0, screen_render.width, screen_render.height,
+ IDENTITY, 0, 0, focuses)
+
+# The result of focus_at_point for a modal render. This overrides any
+# specific focus from below us.
+Modal = object()
+
+def focus_at_point(x, y):
+ """
+ Returns a focus object corresponding to the uppermost displayable
+ at point, or None if nothing focusable is at point.
+ """
+
+ if screen_render is None:
+ return None
+
+ cf = screen_render.focus_at_point(x, y)
+ if cf is None or cf is Modal:
+ return None
+ else:
+ d, arg = cf
+ return renpy.display.focus.Focus(d, arg, None, None, None, None)
+
+
+def mutated_surface(surf):
+ """
+ Called to indicate that the given surface has changed.
+ """
+
+ renpy.display.draw.mutated_surface(surf)
+
+
+def render_screen(root, width, height):
+ """
+ Renders `root` (a displayable) as the root of a screen with the given
+ `width` and `height`.
+ """
+
+
+
+ global old_screen_render
+ global screen_render
+ global invalidated
+ global frame_time
+
+ frame_time = renpy.display.interface.frame_time
+
+ rv = render(root, width, height, 0, 0)
+ screen_render = rv
+
+ invalidated = False
+
+ rv.is_opaque()
+
+ return rv
+
+def mark_sweep():
+ """
+ This performs mark-and-sweep garbage collection on the live_renders
+ list.
+ """
+
+ global live_renders
+
+ cdef list worklist
+ cdef int i
+ cdef Render r, j
+
+ worklist = [ ]
+
+ if screen_render is not None:
+ worklist.append(screen_render)
+
+ i = 0
+
+ while i < len(worklist):
+ r = worklist[i]
+
+ for j in r.depends_on_list:
+ if not j.mark:
+ j.mark = True
+ worklist.append(j)
+
+ i += 1
+
+ for r in live_renders:
+ if not r.mark:
+ r.kill_cache()
+ else:
+ r.mark = False
+
+ live_renders = worklist
+
+def compute_subline(sx0, sw, cx0, cw):
+ """
+ Given a source line (start sx0, width sw) and a crop line (cx0, cw),
+ return three things:
+
+ * The offset of the portion of the source line that overlaps with
+ the crop line, relative to the crop line.
+ * The offset of the portion of the source line that overlaps with the
+ the crop line, relative to the source line.
+ * The length of the overlap in pixels. (can be <= 0)
+ """
+
+ sx1 = sx0 + sw
+ cx1 = cx0 + cw
+
+ if sx0 > cx0:
+ start = sx0
+ else:
+ start = cx0
+
+ offset = start - cx0
+ crop = start - sx0
+
+ if sx1 < cx1:
+ width = sx1 - start
+ else:
+ width = cx1 - start
+
+
+ return offset, crop, width
+
+
+
+
+# Possible operations that can be done as part of a render.
+BLIT = 0
+DISSOLVE = 1
+IMAGEDISSOLVE = 2
+PIXELLATE = 3
+
+cdef class Render:
+
+ def __init__(Render self, float width, float height, draw_func=None, layer_name=None, bint opaque=None): #@DuplicatedSignature
+ """
+ Creates a new render corresponding to the given widget with
+ the specified width and height.
+
+ If `layer_name` is given, then this render corresponds to a
+ layer.
+ """
+
+ # The mark bit, used for mark/sweep-style garbage collection of
+ # renders.
+ self.mark = False
+
+ # Is has this render been removed from the cache?
+ self.cache_killed = False
+
+
+ self.width = width
+ self.height = height
+
+ self.layer_name = layer_name
+
+ # A list of (surface/render, xoffset, yoffset, focus, main) tuples, ordered from
+ # back to front.
+ self.children = [ ]
+
+ # The set of renders that either have us as children, or depend on
+ # us.
+ self.parents = set()
+
+ # The renders we depend on, including our children.
+ self.depends_on_list = [ ]
+
+ # The operation we're performing. (BLIT, DISSOLVE, OR IMAGE_DISSOLVE)
+ self.operation = BLIT
+
+ # The fraction of the operation that is complete.
+ self.operation_complete = 0.0
+
+ # Should the dissolve operations preserve alpha?
+ self.operation_alpha = False
+
+ # The parameter to the operation.
+ self.operation_parameter = 0
+
+ # Forward is used to transform from screen coordinates to child
+ # coordinates.
+ # Reverse is used to transform from child coordinates to screen
+ # coordinates.
+ #
+ # For performance reasons, these aren't used to transform the
+ # x and y offsets found in self.children. Those offsets should
+ # be of the (0, 0) point in the child coordinate space.
+ self.forward = None
+ self.reverse = None
+
+ # This is used to adjust the alpha of children of this render.
+ self.alpha = 1
+
+ # A list of focus regions in this displayable.
+ self.focuses = None
+
+ # Other renders that we should pass focus onto.
+ self.pass_focuses = None
+
+ # The displayable(s) that this is a render of. (Set by render)
+ self.render_of = [ ]
+
+ # If set, this is a function that's called to draw this render
+ # instead of the default.
+ self.draw_func = draw_func
+
+ # Is this displayable opaque? (May be set on init, or later on
+ # if we have opaque children.) This may be True, False, or None
+ # to indicate we don't know yet.
+ self.opaque = opaque
+
+ # A list of our visible children. (That is, children above and
+ # including our uppermost opaque child.) If nothing is opaque,
+ # includes all children.
+ self.visible_children = self.children
+
+ # Should children be clipped to a rectangle?
+ self.clipping = False
+
+ # Caches of the texture created by rendering this surface.
+ self.surface = None
+ self.alpha_surface = None
+
+ # Cache of the texture created by rendering this surface at half size.
+ # (This is set in gldraw.)
+ self.half_cache = None
+
+ # Are we modal?
+ self.modal = False
+
+ live_renders.append(self)
+
+ def __repr__(self): #@DuplicatedSignature
+ return "<Render %x of %r>" % (id(self), self.render_of)
+
+ def __getstate__(self): #@DuplicatedSignature
+ if renpy.config.developer:
+ raise Exception("Can't pickle a Render.")
+ else:
+ return { }
+
+ def __setstate__(self, state): #@DuplicatedSignature
+ return
+
+ cpdef int blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+ """
+ Blits `source` (a Render or Surface) to this Render, offset by
+ xo and yo.
+
+ If `focus` is true, then focuses are added from the child to the
+ parent.
+
+ This will only blit on integer pixel boundaries.
+ """
+
+ (xo, yo) = pos
+
+ if source is self:
+ raise Exception("Blitting to self.")
+
+ xo = int(xo)
+ yo = int(yo)
+
+ if index is None:
+ self.children.append((source, xo, yo, focus, main))
+ else:
+ self.children.insert(index, (source, xo, yo, focus, main))
+
+ if isinstance(source, Render):
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ return 0
+
+ cpdef int subpixel_blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+ """
+ Blits `source` (a Render or Surface) to this Render, offset by
+ xo and yo.
+
+ If `focus` is true, then focuses are added from the child to the
+ parent.
+
+ This blits at fractional pixel boundaries.
+ """
+
+ (xo, yo) = pos
+
+ xo = float(xo)
+ yo = float(yo)
+
+ if index is None:
+ self.children.append((source, xo, yo, focus, main))
+ else:
+ self.children.insert(index, (source, xo, yo, focus, main))
+
+ if isinstance(source, Render):
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ return 0
+
+ def get_size(self):
+ """
+ Returns the size of this Render, a mostly ficticious value
+ that's taken from the inputs to the constructor. (As in, we
+ don't clip to this size.)
+ """
+
+ return self.width, self.height
+
+
+ def render_to_texture(self, alpha=True):
+ """
+ Returns a texture constructed from this render. This may return
+ a cached textue, if one has already been rendered.
+
+ `alpha` is a hint that controls if the surface should have
+ alpha or not.
+ """
+
+ if alpha:
+ if self.alpha_surface is not None:
+ return self.alpha_surface
+ else:
+ if self.surface is not None:
+ return self.surface
+
+ rv = None
+
+ opaque = self.is_opaque()
+
+ # If we can, reuse a child's texture.
+ if opaque or alpha:
+
+ if not self.forward and len(self.children) == 1:
+ child, x, y, focus, main = self.children[0]
+ cw, ch = child.get_size()
+ if x <= 0 and y <= 0 and cw + x >= self.width and ch + y >= self.height:
+ # Our single child overlaps us.
+ if isinstance(child, Render):
+ child = child.render_to_texture(alpha)
+
+ if x != 0 or y != 0 or cw != self.width or ch != self.height:
+ rv = child.subsurface((-x, -y, self.width, self.height))
+ else:
+ rv = child
+
+ # Otherwise, render to a texture.
+ if rv is None:
+ # is_opaque has already been called.
+ rv = renpy.display.draw.render_to_texture(self, alpha)
+
+ # Stash and return the surface.
+ if alpha:
+ self.alpha_surface = rv
+ else:
+ self.surface = rv
+
+ return rv
+
+ pygame_surface = render_to_texture
+
+ def subsurface(self, rect, focus=False):
+ """
+ Returns a subsurface of this render. If `focus` is true, then
+ the focuses are copied from this render to the child.
+ """
+
+ (x, y, w, h) = rect
+ rv = Render(w, h)
+
+ reverse = self.reverse
+
+ # This doesn't actually make a subsurface, as we can't easily do
+ # so for non-rectangle-aligned renders.
+ if (reverse is not None) and (
+ reverse.xdx != 1.0 or
+ reverse.xdy != 0.0 or
+ reverse.ydx != 0.0 or
+ reverse.ydy != 1.0):
+
+ rv.clipping = True
+ rv.blit(self, (-x, -y), focus=focus, main=True)
+ return rv
+
+ # This is the path that executes for rectangle-aligned surfaces,
+ # making an actual subsurface.
+
+ for child, cx, cy, cfocus, cmain in self.children:
+
+ cw, ch = child.get_size()
+ xo, cx, cw = compute_subline(cx, cw, x, w)
+ yo, cy, ch = compute_subline(cy, ch, y, h)
+
+ if cw <= 0 or ch <= 0:
+ continue
+
+ crop = (cx, cy, cw, ch)
+ offset = (xo, yo)
+
+ if isinstance(child, Render):
+ newchild = child.subsurface(crop, focus=focus)
+ newchild.render_of = child.render_of[:]
+ else:
+ newchild = child.subsurface(crop)
+ renpy.display.draw.mutated_surface(newchild)
+
+ rv.blit(newchild, offset, focus=cfocus, main=cmain)
+
+ if focus and self.focuses:
+
+ for (d, arg, xo, yo, fw, fh, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+ continue
+
+ xo, cx, fw = compute_subline(xo, fw, x, w)
+ yo, cy, fh = compute_subline(yo, fh, y, h)
+
+ if cw <= 0 or ch <= 0:
+ continue
+
+ if mx is not None:
+
+ mw, mh = mask.get_size()
+
+ mx, mcx, mw = compute_subline(mx, mw, x, w)
+ my, mcy, mh = compute_subline(my, mh, y, h)
+
+ if mw <= 0 or mh <= 0:
+ mx = None
+ my = None
+ mask = None
+ else:
+ mask = mask.subsurface((mcx, mcy, mw, mh))
+
+ rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+
+ rv.depends_on(self)
+ rv.alpha = self.alpha
+ rv.operation = self.operation
+ rv.operation_alpha = self.operation_alpha
+ rv.operation_complete = self.operation_complete
+
+ return rv
+
+
+ def depends_on(self, source, focus=False):
+ """
+ Used to indicate that this render depends on another
+ render. Useful, for example, if we use pygame_surface to make
+ a surface, and then blit that surface into another render.
+ """
+
+ if source is self:
+ raise Exception("Render depends on itself.")
+
+ self.depends_on_list.append(source)
+ source.parents.add(self)
+
+ if focus:
+ if self.pass_focuses is None:
+ self.pass_focuses = [ source ]
+ else:
+ self.pass_focuses.append(source)
+
+
+ def kill_cache(self):
+ """
+ Removes this render and its transitive parents from the cache.
+ """
+
+ if self.cache_killed:
+ return
+
+ self.cache_killed = True
+
+ for i in self.parents:
+ i.kill_cache()
+
+ self.parents.clear()
+
+ for i in self.depends_on_list:
+ if not i.cache_killed:
+ i.parents.discard(self)
+
+ for ro in self.render_of:
+ cache = render_cache[ro]
+ for k, v in cache.items():
+ if v is self:
+ del cache[k]
+
+ if not cache:
+ del render_cache[ro]
+
+ def kill(self):
+ """
+ Retained for compatibility.
+ """
+
+ def add_focus(self, d, arg=None, x=0, y=0, w=None, h=None, mx=None, my=None, mask=None):
+ """
+ This is called to indicate a region of the screen that can be
+ focused.
+
+ `d` - the displayable that is being focused.
+ `arg` - an argument.
+
+ The rest of the parameters are a rectangle giving the portion of
+ this region corresponding to the focus. If they are all None, than
+ this focus is assumed to be the singular full-screen focus.
+ """
+
+ if mask is not None and mask is not self:
+ self.depends_on(mask)
+
+ t = (d, arg, x, y, w, h, mx, my, mask)
+
+ if self.focuses is None:
+ self.focuses = [ t ]
+ else:
+ self.focuses.append(t)
+
+ def take_focuses(self, cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses): #@DuplicatedSignature
+ """
+ This adds to focuses Focus objects corresponding to the focuses
+ added to this object and its children, transformed into screen
+ coordinates.
+
+ `cminx`, `cminy`, `cmaxx`, `cmaxy` - The clipping rectangle.
+ `reverse` - The transform from render to screen coordinates.
+ `x`, `y` - The offset of the upper-left corner of the render.
+ `focuses` - The list of focuses to add to.
+ """
+
+ if self.modal:
+ focuses[:] = [ ]
+
+ if self.reverse:
+ reverse = reverse * self.reverse
+
+ if self.focuses:
+
+ for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ focuses.append(renpy.display.focus.Focus(d, arg, None, None, None, None))
+ continue
+
+ x1, y1 = reverse.transform(xo, yo)
+ x2, y2 = reverse.transform(xo + w, yo + h)
+
+ minx = min(x1, x2) + x
+ miny = min(y1, y2) + y
+ maxx = max(x1, x2) + x
+ maxy = max(y1, y2) + y
+
+ minx = max(minx, cminx)
+ miny = max(miny, cminy)
+ maxx = min(maxx, cmaxx)
+ maxy = min(maxy, cmaxy)
+
+ if minx >= maxx or miny >= maxy:
+ continue
+
+ focuses.append(renpy.display.focus.Focus(d, arg, minx, miny, maxx - minx, maxy - miny))
+
+ if self.clipping:
+ cminx = max(cminx, x)
+ cminy = max(cminy, y)
+ cmaxx = min(cmaxx, x + self.width)
+ cmaxy = min(cmaxx, x + self.height)
+
+ for child, xo, yo, focus, main in self.children:
+ if not focus or not isinstance(child, Render):
+ continue
+
+ xo, yo = reverse.transform(xo, yo)
+ child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x + xo, y + yo, focuses)
+
+ if self.pass_focuses:
+ for child in self.pass_focuses:
+ child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses)
+
+ def focus_at_point(self, x, y): #@DuplicatedSignature
+ """
+ This returns the focus of this object at the given point.
+ """
+
+ if self.clipping:
+ if x < 0 or x >= self.width or y < 0 or y >= self.height:
+ return None
+
+ rv = None
+
+ if self.focuses:
+ for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+ if xo is None:
+ continue
+
+ elif mx is not None:
+ cx = x - mx
+ cy = y - my
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ if mask.is_pixel_opaque(cx, cy):
+ rv = d, arg
+
+ elif xo <= x < xo + w and yo <= y < yo + h:
+ rv = d, arg
+
+ for child, xo, yo, focus, main in self.children:
+
+ if not focus or not isinstance(child, Render):
+ continue
+
+ cx = x - xo
+ cy = y - yo
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ cf = child.focus_at_point(cx, cy)
+ if cf is not None:
+ rv = cf
+
+ if self.pass_focuses:
+ for child in self.pass_focuses:
+ cf = child.focus_at_point(x, y)
+ if cf is not None:
+ rv = cf
+
+ if rv is None and self.modal:
+ rv = Modal
+
+ return rv
+
+
+ def main_displayables_at_point(self, x, y, layers, depth=None):
+ """
+ Returns the displayable at `x`, `y` on one of the layers in
+ the set or list `layers`.
+ """
+
+ rv = [ ]
+
+ if x < 0 or y < 0 or x >= self.width or y >= self.height:
+ return rv
+
+ if depth is not None:
+ for d in self.render_of:
+ rv.append((depth, self.width, self.height, d))
+ depth += 1
+ elif self.layer_name in layers:
+ depth = 0
+
+ for (child, xo, yo, focus, main) in self.children:
+ if not main or not isinstance(child, Render):
+ continue
+
+ cx = x - xo
+ cy = y - yo
+
+ if self.forward:
+ cx, cy = self.forward.transform(cx, cy)
+
+ cf = child.main_displayables_at_point(cx, cy, layers, depth)
+ rv.extend(cf)
+
+ return rv
+
+
+ def is_opaque(self):
+ """
+ Returns true if this displayable is opaque, or False otherwise.
+ Also sets self.visible_children.
+ """
+
+ if self.opaque is not None:
+ return self.opaque
+
+ # A rotated image is never opaque. (This isn't actually true, but it
+ # saves us from the expensive calculations require to prove it is.)
+ if self.forward:
+ self.opaque = False
+ return False
+
+ rv = False
+ vc = [ ]
+
+ for i in self.children:
+ child, xo, yo, focus, main = i
+
+ if xo <= 0 and yo <= 0:
+ cw, ch = child.get_size()
+ if cw + xo < self.width or ch + yo < self.height:
+ if child.is_opaque():
+ vc = [ ]
+ rv = True
+
+ vc.append(i)
+
+ self.visible_children = vc
+ self.opaque = rv
+ return rv
+
+
+ def is_pixel_opaque(self, x, y):
+ """
+ Determine if the pixel at x and y is opaque or not.
+ """
+
+ if x < 0 or y < 0 or x >= self.width or y >= self.height:
+ return False
+
+ if self.is_opaque():
+ return True
+
+ return renpy.display.draw.is_pixel_opaque(self, x, y)
+
+
+ def fill(self, color):
+ """
+ Fills this Render with the given color.
+ """
+
+ color = renpy.easy.color(color)
+ solid = renpy.display.imagelike.Solid(color)
+ surf = render(solid, self.width, self.height, 0, 0)
+ self.blit(surf, (0, 0), focus=False, main=False)
+
+
+ def canvas(self):
+ """
+ Returns a canvas object that draws to this Render.
+ """
+
+ surf = renpy.display.pgrender.surface((self.width, self.height), True)
+
+ mutated_surface(surf)
+
+ self.blit(surf, (0, 0))
+
+ return Canvas(surf)
+
+
+class Canvas(object):
+
+ def __init__(self, surf): #@DuplicatedSignature
+ self.surf = surf
+
+ def rect(self, color, rect, width=0):
+
+ try:
+ blit_lock.acquire()
+ pygame.draw.rect(self.surf,
+ renpy.easy.color(color),
+ rect,
+ width)
+ finally:
+ blit_lock.release()
+
+ def polygon(self, color, pointlist, width=0):
+ try:
+ blit_lock.acquire()
+ pygame.draw.polygon(self.surf,
+ renpy.easy.color(color),
+ pointlist,
+ width)
+ finally:
+ blit_lock.release()
+
+ def circle(self, color, pos, radius, width=0):
+
+ try:
+ blit_lock.acquire()
+ pygame.draw.circle(self.surf,
+ renpy.easy.color(color),
+ pos,
+ radius,
+ width)
+
+ finally:
+ blit_lock.release()
+
+ def ellipse(self, color, rect, width=0):
+ try:
+ blit_lock.acquire()
+ pygame.draw.ellipse(self.surf,
+ renpy.easy.color(color),
+ rect,
+ width)
+ finally:
+ blit_lock.release()
+
+
+ def arc(self, color, rect, start_angle, stop_angle, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.arc(self.surf,
+ renpy.easy.color(color),
+ rect,
+ start_angle,
+ stop_angle,
+ width)
+ finally:
+ blit_lock.release()
+
+
+ def line(self, color, start_pos, end_pos, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.line(self.surf,
+ renpy.easy.color(color),
+ start_pos,
+ end_pos,
+ width)
+ finally:
+ blit_lock.release()
+
+ def lines(self, color, closed, pointlist, width=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.lines(self.surf,
+ renpy.easy.color(color),
+ closed,
+ pointlist,
+ width)
+ finally:
+ blit_lock.release()
+
+ def aaline(self, color, startpos, endpos, blend=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.aaline(self.surf,
+ renpy.easy.color(color),
+ startpos,
+ endpos,
+ blend)
+ finally:
+ blit_lock.release()
+
+ def aalines(self, color, closed, pointlist, blend=1):
+ try:
+ blit_lock.acquire()
+ pygame.draw.aalines(self.surf,
+ renpy.easy.color(color),
+ closed,
+ pointlist,
+ blend)
+ finally:
+ blit_lock.release()
diff --git a/unrpyc/renpy/display/scale.py b/unrpyc/renpy/display/scale.py
new file mode 100644
index 0000000..c9efbcc
--- /dev/null
+++ b/unrpyc/renpy/display/scale.py
@@ -0,0 +1,109 @@
+# 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 used to hack pygame to support resolution-scaling. Now it just kinda
+# sits here, to provide compatibility with what it used to be.
+
+import pygame
+import renpy.display
+import renpy.display.pgrender as pgrender
+
+import _renpy
+
+##############################################################################
+# The scaling API that's used if we don't enable scaling.
+
+# Gets the real pygame surface.
+def real(s):
+ return s
+
+# Scales the number, n.
+def scale(n):
+ return n
+
+def real_bilinear(src, size):
+ rv = pgrender.surface_unscaled(size, src)
+ renpy.display.module.bilinear_scale(src, rv)
+ return rv
+
+# Does pygame.transform.scale.
+def real_transform_scale(surf, size):
+ return pgrender.transform_scale_unscaled(surf, size)
+
+# Loads an image, without scaling it.
+def image_load_unscaled(f, hint, convert=True):
+ rv = pgrender.load_image_unscaled(f, hint)
+ return rv
+
+# Saves an image without rescaling.
+def image_save_unscaled(surf, filename):
+ pygame.image.save(surf, renpy.exports.fsencode(filename))
+
+# Scales down a surface.
+def surface_scale(full):
+ return full
+
+real_renpy_pixellate = _renpy.pixellate
+real_renpy_transform = _renpy.transform
+
+def real_smoothscale(src, size, dest=None):
+ """
+ This scales src up or down to size. This uses both the pixellate
+ and the transform operations to handle the scaling.
+ """
+
+ width, height = size
+ srcwidth, srcheight = src.get_size()
+ iwidth, iheight = srcwidth, srcheight
+
+ if dest is None:
+ dest = pgrender.surface_unscaled(size, src)
+
+ if width == 0 or height == 0:
+ return dest
+
+ xshrink = 1
+ yshrink = 1
+
+ while iwidth >= width * 2:
+ xshrink *= 2
+ iwidth /= 2
+
+ while iheight >= height * 2:
+ yshrink *= 2
+ iheight /= 2
+
+ if iwidth != srcwidth or iheight != srcheight:
+ inter = pgrender.surface_unscaled((iwidth, iheight), src)
+ real_renpy_pixellate(src, inter, xshrink, yshrink, 1, 1)
+ src = inter
+
+ real_renpy_transform(src, dest,
+ 0, 0,
+ 1.0 * iwidth / width , 0,
+ 0, 1.0 * iheight / height,
+ precise=1,
+ )
+
+ return dest
+
+smoothscale = real_smoothscale
+
diff --git a/unrpyc/renpy/display/screen.py b/unrpyc/renpy/display/screen.py
new file mode 100644
index 0000000..8fd529b
--- /dev/null
+++ b/unrpyc/renpy/display/screen.py
@@ -0,0 +1,639 @@
+# 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.
+
+import renpy.display
+
+class Screen(renpy.object.Object):
+ """
+ A screen is a collection of widgets that are displayed together.
+ This class stores information about the screen.
+ """
+
+ def __init__(self,
+ name,
+ function,
+ modal="False",
+ zorder="0",
+ tag=None,
+ predict=None,
+ variant=None,
+ parameters=False):
+
+ # The name of this screen.
+ if isinstance(name, str):
+ name = tuple(name.split())
+
+ self.name = name
+
+ screens[name[0], variant] = self
+
+ # The function that is called to display this screen.
+ self.function = function
+
+ # Expression: Are we modal? (A modal screen ignores screens under it.)
+ self.modal = modal
+
+ # Expression: Our zorder.
+ self.zorder = zorder
+
+ # The tag associated with the screen.
+ self.tag = tag or name[0]
+
+ # Can this screen be predicted?
+ if predict is None:
+ predict = renpy.config.predict_screens
+
+ self.predict = predict
+
+ # True if this screen takes parameters via _args and _kwargs.
+ self.parameters = parameters
+
+
+class ScreenDisplayable(renpy.display.layout.Container):
+ """
+ A screen is a collection of widgets that are displayed together. This
+ class is responsible for managing the display of a screen.
+ """
+
+ nosave = [ 'screen', 'child', 'transforms', 'widgets', 'old_widgets', 'old_transforms' ]
+
+ restarting = False
+
+ def after_setstate(self):
+ self.screen = get_screen_variant(self.screen_name[0])
+ self.child = None
+ self.transforms = { }
+ self.widgets = { }
+ self.old_widgets = None
+ self.old_transforms = None
+
+ def __init__(self, screen, tag, layer, widget_properties={}, scope={}, **properties):
+
+ super(ScreenDisplayable, self).__init__(**properties)
+
+ # Stash the properties, so we can re-create the screen.
+ self.properties = properties
+
+ # The screen, and it's name. (The name is used to look up the
+ # screen on save.)
+ self.screen = screen
+ self.screen_name = screen.name
+
+ # The tag and layer screen was displayed with.
+ self.tag = tag
+ self.layer = layer
+
+ # The scope associated with this statement. This is passed in
+ # as keyword arguments to the displayable.
+ self.scope = renpy.python.RevertableDict(scope)
+
+ # The child associated with this screen.
+ self.child = None
+
+ # Widget properties given to this screen the last time it was
+ # shown.
+ self.widget_properties = widget_properties
+
+ # A map from name to the widget with that name.
+ self.widgets = { }
+
+ if tag and layer:
+ old_screen = get_screen(tag, layer)
+ else:
+ old_screen = None
+
+ # A map from name to the transform with that name. (This is
+ # taken from the old version of the screen, if it exists.
+ if old_screen is not None:
+ self.transforms = old_screen.transforms
+ else:
+ self.transforms = { }
+
+ # What widgets and transforms were the last time this screen was
+ # updated. Used to communicate with the ui module, and only
+ # valid during an update - not used at other times.
+ self.old_widgets = None
+ self.old_transforms = None
+
+ # Should we transfer data from the old_screen? This becomes
+ # true once this screen finishes updating for the first time,
+ # and also while we're using something.
+ self.old_transfers = (old_screen and old_screen.screen_name == self.screen_name)
+
+ # The current transform event, and the last transform event to
+ # be processed.
+ self.current_transform_event = None
+
+ # A dict-set of widgets (by id) that have been hidden from us.
+ self.hidden_widgets = { }
+
+ # Are we hiding?
+ self.hiding = False
+
+ # Are we restarting?
+ self.restarting = False
+
+ # Modal and zorder.
+ self.modal = renpy.python.py_eval(self.screen.modal, locals=self.scope)
+ self.zorder = renpy.python.py_eval(self.screen.zorder, locals=self.scope)
+
+ def __repr__(self):
+ return "<ScreenDisplayable: %r>" % (self.screen_name,)
+
+ def visit(self):
+ return [ self.child ]
+
+ def per_interact(self):
+ renpy.display.render.redraw(self, 0)
+ self.update()
+
+ def set_transform_event(self, event):
+ super(ScreenDisplayable, self).set_transform_event(event)
+ self.current_transform_event = event
+
+ def find_focusable(self, callback, focus_name):
+ if self.child and not self.hiding:
+ self.child.find_focusable(callback, focus_name)
+
+ def _hide(self, st, at, kind):
+
+ if self.hiding:
+ hid = self
+ else:
+ hid = ScreenDisplayable(self.screen, self.tag, self.layer, self.widget_properties, self.scope, **self.properties)
+ hid.transforms = self.transforms.copy()
+ hid.widgets = self.widgets.copy()
+ hid.old_transfers = True
+
+ hid.hiding = True
+
+ hid.current_transform_event = kind
+ hid.update()
+
+ renpy.display.render.redraw(hid, 0)
+
+ rv = None
+
+ # Compute the reverse of transforms and widgets.
+ reverse_transforms = dict((id(v), k) for k, v in hid.transforms.items())
+ reverse_widgets = dict((id(v), k) for k, v in hid.widgets.items())
+
+ # Assumption: the only displayables that can keep us around
+ # are Transforms that handle hide.
+
+ # Iterate over our immediate children, trying to hide them.
+ for d in list(hid.child.children):
+
+ id_d = id(d)
+
+ # If we have a transform, call its _hide method. If that comes
+ # back non-None, store the new transform, and keep us alive.
+ #
+ # Otherwise, remove the child.
+ name = reverse_transforms.get(id_d, None)
+
+ if name is not None:
+ c = d._hide(st, at, kind)
+
+ if c is not None:
+ hid.transforms[name] = c
+ rv = hid
+ else:
+ hid.hidden_widgets[name] = True
+ hid.child.remove(d)
+
+ continue
+
+ # Remove any non-transform children.
+ name = reverse_widgets.get(id_d, None)
+
+ if name is not None:
+ hid.hidden_widgets[name] = True
+ hid.child.remove(d)
+
+ return rv
+
+ def update(self):
+
+ # If we're restarting, do not update - the update can use variables
+ # that are no longer in scope.
+ if self.restarting:
+ if not self.child:
+ self.child = renpy.display.layout.Null()
+
+ return self.widgets
+
+ # Update _current_screen
+ global _current_screen
+ old_screen = _current_screen
+ _current_screen = self
+
+ # Cycle widgets and transforms.
+ self.old_widgets = self.widgets
+ self.old_transforms = self.transforms
+ self.widgets = { }
+ self.transforms = { }
+
+ # Render the child.
+ old_ui_screen = renpy.ui.screen
+ renpy.ui.screen = self
+
+ renpy.ui.detached()
+ self.child = renpy.ui.fixed(focus="_screen_" + "_".join(self.screen_name))
+ self.children = [ self.child ]
+
+ self.scope["_scope"] = self.scope
+ self.scope["_name"] = 0
+
+ self.screen.function(**self.scope)
+
+ renpy.ui.close()
+
+ renpy.ui.screen = old_ui_screen
+ _current_screen = old_screen
+
+ # Visit all the children, to get them started.
+ self.child.visit_all(lambda c : c.per_interact())
+
+ # Finish up.
+ self.old_widgets = None
+ self.old_transforms = None
+ self.old_transfers = True
+
+ if self.current_transform_event:
+
+ for i in self.child.children:
+ i.set_transform_event(self.current_transform_event)
+
+ self.current_transform_event = None
+
+ return self.widgets
+
+ def render(self, w, h, st, at):
+
+ if not self.child:
+ self.update()
+
+ child = renpy.display.render.render(self.child, w, h, st, at)
+
+ rv = renpy.display.render.Render(w, h)
+
+ rv.blit(child, (0, 0), focus=not self.hiding, main=not self.hiding)
+ rv.modal = self.modal and not self.hiding
+
+ return rv
+
+ def get_placement(self):
+ if not self.child:
+ self.update()
+
+ return self.child.get_placement()
+
+ def event(self, ev, x, y, st):
+
+ if self.hiding:
+ return
+
+ global _current_screen
+ old_screen = _current_screen
+ _current_screen = self
+
+ rv = self.child.event(ev, x, y, st)
+
+ _current_screen = old_screen
+
+ if rv is not None:
+ return rv
+
+ if self.modal:
+ raise renpy.display.layout.IgnoreLayers()
+
+
+# The name of the screen that is currently being displayed, or
+# None if no screen is being currently displayed.
+_current_screen = None
+
+# A map from (screen_name, variant) tuples to screen.
+screens = { }
+
+def get_screen_variant(name):
+ """
+ Get a variant screen object for `name`.
+ """
+
+ for i in renpy.config.variants:
+ rv = screens.get((name, i), None)
+ if rv is not None:
+ return rv
+
+ return None
+
+def define_screen(*args, **kwargs):
+ """
+ :doc: screens
+ :args: (name, function, modal="False", zorder="0", tag=None, variant=None)
+
+ Defines a screen with `name`, which should be a string.
+
+ `function`
+ The function that is called to display the screen. The
+ function is called with the screen scope as keyword
+ arguments. It should ignore additional keyword arguments.
+
+ The function should call the ui functions to add things to the
+ screen.
+
+ `modal`
+ A string that, when evaluated, determines of the created
+ screen should be modal. A modal screen prevents screens
+ underneath it from receiving input events.
+
+ `zorder`
+ A string that, when evaluated, should be an integer. The integer
+ controls the order in which screens are displayed. A screen
+ with a greater zorder number is displayed above screens with a
+ lesser zorder number.
+
+ `tag`
+ The tag associated with this screen. When the screen is shown,
+ it replaces any other screen with the same tag. The tag
+ defaults to the name of the screen.
+
+ `predict`
+ If true, this screen can be loaded for image prediction. If false,
+ it can't. Defaults to true.
+
+ `variant`
+ String. Gives the variant of the screen to use.
+
+ """
+
+ Screen(*args, **kwargs)
+
+
+
+def get_screen(name, layer="screens"):
+ """
+ :doc: screens
+
+ Returns the ScreenDisplayable with the given `tag`, on
+ `layer`. If no displayable with the tag is not found, it is
+ interpreted as screen name. If it's still not found, None is returned.
+ """
+
+ if isinstance(name, str):
+ name = tuple(name.split())
+
+ tag = name[0]
+
+ sl = renpy.exports.scene_lists()
+
+ sd = sl.get_displayable_by_tag(layer, tag)
+
+ if sd is None:
+ sd = sl.get_displayable_by_name(layer, name)
+
+ return sd
+
+def has_screen(name):
+ """
+ Returns true if a screen with the given name exists.
+ """
+
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ if not name:
+ return False
+
+ if get_screen_variant(name[0]):
+ return True
+ else:
+ return False
+
+def show_screen(_screen_name, *_args, **kwargs):
+ """
+ :doc: screens
+
+ The programmatic equivalent of the show screen statement.
+
+ Shows the named screen. This takes the following keyword arguments:
+
+ `_screen_name`
+ The name of the screen to show.
+ `_layer`
+ The layer to show the screen on.
+ `_tag`
+ The tag to show the screen with. If not specified, defaults to
+ the tag associated with the screen. It that's not specified,
+ defaults to the name of the screen.,
+ `_widget_properties`
+ A map from the id of a widget to a property name -> property
+ value map. When a widget with that id is shown by the screen,
+ the specified properties are added to it.
+ `_transient`
+ If true, the screen will be automatically hidden at the end of
+ the current interaction.
+
+ Keyword arguments not beginning with underscore (_) are used to
+ initialize the screen's scope.
+ """
+
+ _layer = kwargs.pop("_layer", "screens")
+ _tag = kwargs.pop("_tag", None)
+ _widget_properties = kwargs.pop("_widget_properties", {})
+ _transient = kwargs.pop("_transient", False)
+
+ name = _screen_name
+
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ screen = get_screen_variant(name[0])
+
+ if screen is None:
+ raise Exception("Screen %s is not known.\n" % (name[0],))
+
+ if _tag is None:
+ _tag = screen.tag
+
+ scope = { }
+
+ if screen.parameters:
+ scope["_kwargs" ] = kwargs
+ scope["_args"] = _args
+ else:
+ scope.update(kwargs)
+
+ d = ScreenDisplayable(screen, _tag, _layer, _widget_properties, scope)
+ renpy.exports.show(name, tag=_tag, what=d, layer=_layer, zorder=d.zorder, transient=_transient, munge_name=False)
+
+
+def predict_screen(_screen_name, *_args, **kwargs):
+ """
+ Predicts the displayables that make up the given screen.
+
+ `_screen_name`
+ The name of the screen to show.
+ `_widget_properties`
+ A map from the id of a widget to a property name -> property
+ value map. When a widget with that id is shown by the screen,
+ the specified properties are added to it.
+
+ Keyword arguments not beginning with underscore (_) are used to
+ initialize the screen's scope.
+ """
+
+ _widget_properties = kwargs.pop("_widget_properties", {})
+ _scope = kwargs.pop
+
+ kwargs["_kwargs" ] = kwargs.copy()
+ kwargs["_args"] = _args
+
+ name = _screen_name
+
+ if renpy.config.debug_image_cache:
+ renpy.display.ic_log.write("Predict screen %s", name)
+
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ screen = get_screen_variant(name[0])
+
+ scope = { }
+
+ if screen.parameters:
+ scope["_kwargs" ] = kwargs
+ scope["_args"] = _args
+ else:
+ scope.update(kwargs)
+
+ try:
+
+ if screen is None:
+ raise Exception("Screen %s is not known.\n" % (name[0],))
+
+ if not screen.predict:
+ return
+
+ d = ScreenDisplayable(screen, None, None, _widget_properties, scope)
+
+ d.update()
+ renpy.display.predict.displayable(d)
+
+ except:
+ if renpy.config.debug_image_cache:
+ import traceback
+
+ print("While predicting screen", screen)
+ traceback.print_exc()
+
+ renpy.ui.reset()
+
+
+def hide_screen(tag, layer='screens'):
+ """
+ :doc: screens
+
+ The programmatic equivalent of the hide screen statement.
+
+ Hides the screen with `tag` on `layer`.
+ """
+
+ screen = get_screen(tag, layer)
+
+ if screen is not None:
+ renpy.exports.hide(screen.tag, layer=layer)
+
+def use_screen(_screen_name, *_args, **kwargs):
+
+ _name = kwargs.pop("_name", ())
+ _scope = kwargs.pop("_scope", { })
+
+ name = _screen_name
+
+ if not isinstance(name, tuple):
+ name = tuple(name.split())
+
+ screen = get_screen_variant(name[0])
+
+ if screen is None:
+ raise Exception("Screen %r is not known." % name)
+
+ old_transfers = _current_screen.old_transfers
+ _current_screen.old_transfers = True
+
+ scope = _scope.copy()
+
+ if screen.parameters:
+ scope["_kwargs"] = kwargs
+ scope["_args"] = _args
+ else:
+ scope.update(kwargs)
+
+ scope["_scope"] = scope
+ scope["_name"] = (_name, name)
+
+ screen.function(**scope)
+
+ _current_screen.old_transfers = old_transfers
+
+def current_screen():
+ return _current_screen
+
+def get_widget(screen, id, layer='screens'): #@ReservedAssignment
+ """
+ :doc: screens
+
+ From the `screen` on `layer`, returns the widget with
+ `id`. Returns None if the screen doesn't exist, or there is no
+ widget with that id on the screen.
+ """
+
+ if screen is None:
+ screen = current_screen()
+ else:
+ screen = get_screen(screen, layer)
+
+ if not isinstance(screen, ScreenDisplayable):
+ return None
+
+ if screen.child is None:
+ screen.update()
+
+ rv = screen.widgets.get(id, None)
+ return rv
+
+def before_restart():
+ """
+ This is called before Ren'Py restarts to put the screens into restart
+ mode, which prevents crashes due to variables being used that are no
+ longer defined.
+ """
+
+ for k, layer in renpy.display.interface.old_scene.items():
+ if k is None:
+ continue
+
+ for i in layer.children:
+ if isinstance(i, ScreenDisplayable):
+ i.restarting = True
+
diff --git a/unrpyc/renpy/display/swdraw.py b/unrpyc/renpy/display/swdraw.py
new file mode 100644
index 0000000..d3ecb51
--- /dev/null
+++ b/unrpyc/renpy/display/swdraw.py
@@ -0,0 +1,1102 @@
+# 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.
+
+import renpy.display
+import pygame
+import math
+import weakref
+import time
+import os
+
+from renpy.display.render import blit_lock, IDENTITY, BLIT, DISSOLVE, IMAGEDISSOLVE, PIXELLATE
+
+# A map from cached surface to rle version of cached surface.
+rle_cache = weakref.WeakKeyDictionary()
+
+class Clipper(object):
+ """
+ This is used to calculate the clipping rectangle and update rectangles
+ used for a particular draw of the screen.
+ """
+
+ def __init__(self):
+
+ # Lists of (x0, y0, x1, y1, clip, surface, transform) tuples,
+ # representing how a displayable is drawn to the screen.
+ self.blits = [ ]
+ self.old_blits = [ ]
+
+ # Sets of (x0, y0, x1, y1) tuples, representing areas that
+ # aren't part of any displayable.
+ self.forced = set()
+ self.old_forced = set()
+
+ # The set of surfaces that have been mutated recently.
+ self.mutated = set()
+
+ def compute(self, full_redraw):
+ """
+ This returns a clipping rectangle, and a list of update rectangles
+ that cover the changes between the old and new frames.
+ """
+
+ # First, get things out of the fields, and update them. This
+ # allows us to just return without having to do any cleanup
+ # code.
+ bl0 = self.old_blits
+ bl1 = self.blits
+ old_forced = self.old_forced
+ forced = self.forced
+ mutated = self.mutated
+
+ self.old_blits = bl1
+ self.blits = [ ]
+ self.old_forced = forced
+ self.forced = set()
+ self.mutated = set()
+
+ sw = renpy.config.screen_width
+ sh = renpy.config.screen_height
+ sa = sw * sh
+
+ # A tuple representing the size of the fullscreen.
+ fullscreen = (0, 0, sw, sh)
+
+ # Check to see if a full redraw has been forced, and return
+ # early.
+ if full_redraw:
+ return fullscreen, [ fullscreen ]
+
+ # Quick checks to see if a dissolve is happening, or something like
+ # that.
+ changes = forced | old_forced
+
+ if fullscreen in changes:
+ return fullscreen, [ fullscreen ]
+
+ # Compute the differences between the two sets, and add those
+ # to changes.
+ i0 = 0
+ i1 = 0
+ bl1set = set(bl1)
+
+ while True:
+ if i0 >= len(bl0) or i1 >= len(bl1):
+ break
+
+ b0 = bl0[i0]
+ b1 = bl1[i1]
+
+ if b0 == b1:
+ if id(b0[5]) in mutated:
+ changes.add(b0[:5])
+
+ i0 += 1
+ i1 += 1
+
+ elif b0 not in bl1set:
+ changes.add(b0[:5])
+ i0 += 1
+
+ else:
+ changes.add(b1[:5])
+ i1 += 1
+
+ changes.update(i[:5] for i in bl0[i0:])
+ changes.update(i[:5] for i in bl1[i1:])
+
+ # No changes? Quit.
+ if not changes:
+ return None, [ ]
+
+ # Compute the sizes of the updated rectangles.
+ sized = [ ]
+
+ for x0, y0, x1, y1, (sx0, sy0, sx1, sy1) in changes:
+
+ # Round up by a pixel, to prevent visual artifacts when scaled down.
+ x1 += 1
+ y1 += 1
+
+ if x0 < sx0:
+ x0 = sx0
+ if y0 < sy0:
+ y0 = sy0
+ if x1 > sx1:
+ x1 = sx1
+ if y1 > sy1:
+ y1 = sy1
+
+ w = x1 - x0
+ h = y1 - y0
+
+ if w <= 0 or h <= 0:
+ continue
+
+ area = w * h
+
+ if area >= sa:
+ return fullscreen, [ fullscreen ]
+
+ sized.append((area, x0, y0, x1, y1))
+
+ sized.sort()
+
+ # The list of non-contiguous updates.
+ noncont = [ ]
+
+ # The total area of noncont.
+ nca = 0
+
+ # Pick the largest area, merge with all overlapping smaller areas, repeat
+ # until no merge possible.
+ while sized:
+ area, x0, y0, x1, y1 = sized.pop()
+
+
+ merged = False
+
+ if nca + area >= sa:
+ return (0, 0, sw, sh), [ (0, 0, sw, sh) ]
+
+ i = 0
+
+ while i < len(sized):
+ _iarea, ix0, iy0, ix1, iy1 = sized[i]
+
+ if (x0 <= ix0 <= x1 or x0 <= ix1 <= x1) and \
+ (y0 <= iy0 <= y1 or y0 <= iy1 <= y1):
+
+ merged = True
+ x0 = min(x0, ix0)
+ x1 = max(x1, ix1)
+ y0 = min(y0, iy0)
+ y1 = max(y1, iy1)
+
+ area = (x1 - x0) * (y1 - y0)
+
+ sized.pop(i)
+
+ else:
+ i += 1
+
+ if merged:
+ sized.append((area, x0, y0, x1, y1))
+ else:
+ noncont.append((x0, y0, x1, y1))
+ nca += area
+
+ if not noncont:
+ return None, [ ]
+
+ x0, y0, x1, y1 = noncont.pop()
+ x0 = int(x0)
+ y0 = int(y0)
+ x1 = int(math.ceil(x1))
+ y1 = int(math.ceil(y1))
+
+ # A list of (x, y, w, h) tuples for each update.
+ updates = [ (x0, y0, x1 - x0, y1 - y0) ]
+
+ for ix0, iy0, ix1, iy1 in noncont:
+
+ ix0 = int(ix0)
+ iy0 = int(iy0)
+ ix1 = int(math.ceil(ix1))
+ iy1 = int(math.ceil(iy1))
+
+ x0 = min(x0, ix0)
+ y0 = min(y0, iy0)
+ x1 = max(x1, ix1)
+ y1 = max(y1, iy1)
+
+ updates.append((ix0, iy0, ix1 - ix0, iy1 - iy0))
+
+ return (x0, y0, x1 - x0, y1 - y0), updates
+
+clippers = [ Clipper() ]
+
+def surface(w, h, alpha):
+ """
+ Creates a surface that shares a pixel format with the screen. The created
+ surface will
+ """
+
+ if alpha:
+ rv = pygame.Surface((w + 4, h + 4), pygame.SRCALPHA)
+ else:
+ rv = pygame.Surface((w + 4, h + 4), 0)
+
+ return rv.subsurface((2, 2, w, h))
+
+def copy_surface(surf):
+ w, h = surf.get_size()
+ rv = surface(w, h, True)
+
+ renpy.display.accelerator.nogil_copy(surf, rv) # @UndefinedVariable
+ return rv
+
+def draw_special(what, dest, x, y):
+ """
+ This handles the special drawing operations, such as dissolve and
+ image dissolve. `x` and `y` are the offsets of the thing to be drawn
+ relative to the destination rectangle, and are always negative.
+ """
+
+ dw, dh = dest.get_size()
+
+ w = min(dw, what.width + x)
+ h = min(dh, what.height + y)
+
+ if w <= 0 or h <= 0:
+ return
+
+ if what.operation == DISSOLVE:
+
+ bottom = what.children[0][0].render_to_texture(True)
+ top = what.children[1][0].render_to_texture(True)
+
+ if what.operation_alpha:
+ target = surface(w, h, True)
+ else:
+ target = dest.subsurface((0, 0, w, h))
+
+ renpy.display.module.blend(
+ bottom.subsurface((-x, -y, w, h)),
+ top.subsurface((-x, -y, w, h)),
+ target,
+ int(what.operation_complete * 255))
+
+ if what.operation_alpha:
+ dest.blit(target, (0, 0))
+
+ elif what.operation == IMAGEDISSOLVE:
+
+ image = what.children[0][0].render_to_texture(True)
+ bottom = what.children[1][0].render_to_texture(True)
+ top = what.children[2][0].render_to_texture(True)
+
+ if what.operation_alpha:
+ target = surface(w, h, True)
+ else:
+ target = dest.subsurface((0, 0, w, h))
+
+ ramplen = what.operation_parameter
+
+ ramp = "\x00" * 256
+
+ for i in range(0, ramplen):
+ ramp += chr(255 * i / ramplen)
+
+ ramp += "\xff" * 256
+
+ step = int( what.operation_complete * (256 + ramplen) )
+ ramp = ramp[step:step+256]
+
+ renpy.display.module.imageblend(
+ bottom.subsurface((-x, -y, w, h)),
+ top.subsurface((-x, -y, w, h)),
+ target,
+ image.subsurface((-x, -y, w, h)),
+ ramp)
+
+ if what.operation_alpha:
+ dest.blit(target, (0, 0))
+
+ elif what.operation == PIXELLATE:
+
+ surf = what.children[0][0].render_to_texture(False)
+
+ px = what.operation_parameter
+
+ renpy.display.module.pixellate(
+ surf.subsurface((-x, -y, w, h)),
+ dest.subsurface((0, 0, w, h)),
+ px, px, px, px)
+
+ else:
+ raise Exception("Unknown operation: %d" % what.operation)
+
+
+def draw(dest, clip, what, xo, yo, screen):
+ """
+ This is the simple draw routine, which only works when alpha is 1.0
+ and the matrices are None. If those aren't the case, draw_complex
+ is used instead.
+
+ `dest` - Either a destination surface, or a clipper.
+ `clip` - If None, we should draw. Otherwise we should clip, and this is
+ the rectangle to clip to.
+ `what` - The Render or Surface we're drawing to.
+ `xo` - The X offset.
+ `yo` - The Y offset.
+ `screen` - True if this is a blit to the screen, False otherwise.
+ """
+
+ if not isinstance(what, renpy.display.render.Render):
+
+ # Pixel-Aligned blit.
+ if isinstance(xo, int) and isinstance(yo, int):
+ if screen:
+ what = rle_cache.get(what, what)
+
+ if clip:
+ w, h = what.get_size()
+ dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None))
+ else:
+ try:
+ blit_lock.acquire()
+ dest.blit(what, (xo, yo))
+ finally:
+ blit_lock.release()
+
+ # Subpixel blit.
+ else:
+ if clip:
+ w, h = what.get_size()
+ dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None))
+ else:
+ renpy.display.module.subpixel(what, dest, xo, yo)
+
+ return
+
+ # Deal with draw functions.
+ if what.operation != BLIT:
+
+ xo = int(xo)
+ yo = int(yo)
+
+ if clip:
+ dx0, dy0, dx1, dy1 = clip
+ dw = dx1 - dx0
+ dh = dy1 - dy0
+ else:
+ dw, dh = dest.get_size()
+
+ if xo >= 0:
+ newx = 0
+ subx = xo
+ else:
+ newx = xo
+ subx = 0
+
+ if yo >= 0:
+ newy = 0
+ suby = yo
+ else:
+ newy = yo
+ suby = 0
+
+ if subx >= dw or suby >= dh:
+ return
+
+ # newx and newy are the offset of this render relative to the
+ # subsurface. They can only be negative or 0, as otherwise we
+ # would make a smaller subsurface.
+
+ subw = min(dw - subx, what.width + newx)
+ subh = min(dh - suby, what.height + newy)
+
+ if subw <= 0 or subh <= 0:
+ return
+
+ if clip:
+ dest.forced.add((subx, suby, subx + subw, suby + subh, clip))
+ else:
+ newdest = dest.subsurface((subx, suby, subw, subh))
+ # what.draw_func(newdest, newx, newy)
+ draw_special(what, newdest, newx, newy)
+
+
+ return
+
+ # Deal with clipping, if necessary.
+ if what.clipping:
+
+ if clip:
+ cx0, cy0, cx1, cy1 = clip
+
+ cx0 = max(cx0, xo)
+ cy0 = max(cy0, yo)
+ cx1 = min(cx1, xo + what.width)
+ cy1 = min(cy1, yo + what.height)
+
+ if cx0 > cx1 or cy0 > cy1:
+ return
+
+ clip = (cx0, cy0, cx1, cy1)
+
+ dest.forced.add(clip + (clip,))
+ return
+
+ else:
+
+ # After this code, x and y are the coordinates of the subsurface
+ # relative to the destination. xo and yo are the offset of the
+ # upper-left corner relative to the subsurface.
+
+ if xo >= 0:
+ x = xo
+ xo = 0
+ else:
+ x = 0
+ # xo = xo
+
+ if yo >= 0:
+ y = yo
+ yo = 0
+ else:
+ y = 0
+ # yo = yo
+
+ dw, dh = dest.get_size()
+
+ width = min(dw - x, what.width + xo)
+ height = min(dh - y, what.height + yo)
+
+ if width < 0 or height < 0:
+ return
+
+ dest = dest.subsurface((x, y, width, height))
+
+ # Deal with alpha and transforms by passing them off to draw_transformed.
+ if what.alpha != 1 or (what.forward is not None and what.forward is not IDENTITY):
+ for child, cxo, cyo, _focus, _main in what.visible_children:
+ draw_transformed(dest, clip, child, xo + cxo, yo + cyo,
+ what.alpha, what.forward, what.reverse)
+ return
+
+ for child, cxo, cyo, _focus, _main in what.visible_children:
+ draw(dest, clip, child, xo + cxo, yo + cyo, screen)
+
+def draw_transformed(dest, clip, what, xo, yo, alpha, forward, reverse):
+
+ # If our alpha has hit 0, don't do anything.
+ if alpha <= 0.003: # (1 / 256)
+ return
+
+ if forward is None:
+ forward = IDENTITY
+ reverse = IDENTITY
+
+ if not isinstance(what, renpy.display.render.Render):
+
+ # Figure out where the other corner of the transformed surface
+ # is on the screen.
+ sw, sh = what.get_size()
+ if clip:
+
+ dx0, dy0, dx1, dy1 = clip
+ dw = dx1 - dx0
+ dh = dy1 - dy0
+
+ else:
+ dw, dh = dest.get_size()
+
+ x0, y0 = 0.0, 0.0
+ x1, y1 = reverse.transform(sw, 0.0)
+ x2, y2 = reverse.transform(sw, sh)
+ x3, y3 = reverse.transform(0.0, sh)
+
+ minx = math.floor(min(x0, x1, x2, x3) + xo)
+ maxx = math.ceil(max(x0, x1, x2, x3) + xo)
+ miny = math.floor(min(y0, y1, y2, y3) + yo)
+ maxy = math.ceil(max(y0, y1, y2, y3) + yo)
+
+ if minx < 0:
+ minx = 0
+ if miny < 0:
+ miny = 0
+
+ if maxx > dw:
+ maxx = dw
+ if maxy > dh:
+ maxy = dh
+
+ if minx > dw or miny > dh or maxx < 0 or maxy < 0:
+ return
+
+ cx, cy = forward.transform(minx - xo, miny - yo)
+
+ if clip:
+
+ dest.blits.append(
+ (minx, miny, maxx + dx0, maxy + dy0, clip, what,
+ (cx, cy,
+ forward.xdx, forward.ydx,
+ forward.xdy, forward.ydy,
+ alpha)))
+
+ else:
+
+ dest = dest.subsurface((minx, miny, maxx - minx, maxy - miny))
+
+ renpy.display.module.transform(
+ what, dest,
+ cx, cy,
+ forward.xdx, forward.ydx,
+ forward.xdy, forward.ydy,
+ alpha, True)
+
+ return
+
+ if what.clipping:
+
+ if reverse.xdy or reverse.ydx:
+ draw_transformed(dest, clip, what.pygame_surface(True), xo, yo, alpha, forward, reverse)
+ return
+
+ width = what.width * reverse.xdx
+ height = what.height * reverse.ydy
+
+ if clip:
+ cx0, cy0, cx1, cy1 = clip
+
+ cx0 = max(cx0, xo)
+ cy0 = max(cy0, yo)
+ cx1 = min(cx1, xo + width)
+ cy1 = min(cy1, yo + height)
+
+ if cx0 > cx1 or cy0 > cy1:
+ return
+
+ clip = (cx0, cy0, cx1, cy1)
+
+ dest.forced.add(clip + (clip,))
+ return
+
+ else:
+
+ # After this code, x and y are the coordinates of the subsurface
+ # relative to the destination. xo and yo are the offset of the
+ # upper-left corner relative to the subsurface.
+
+ if xo >= 0:
+ x = xo
+ xo = 0
+ else:
+ x = 0
+ # xo = xo
+
+ if yo >= 0:
+ y = yo
+ yo = 0
+ else:
+ y = 0
+ # yo = yo
+
+ dw, dh = dest.get_size()
+
+ width = min(dw - x, width + xo)
+ height = min(dh - y, height + yo)
+
+ if width < 0 or height < 0:
+ return
+
+ dest = dest.subsurface((x, y, width, height))
+
+ if what.draw_func or what.operation != BLIT:
+ child = what.pygame_surface(True)
+ draw_transformed(dest, clip, child, xo, yo, alpha, forward, reverse)
+ return
+
+ for child, cxo, cyo, _focus, _main in what.visible_children:
+
+ cxo, cyo = reverse.transform(cxo, cyo)
+
+ if what.forward:
+ child_forward = forward * what.forward
+ child_reverse = what.reverse * reverse
+ else:
+ child_forward = forward
+ child_reverse = reverse
+
+ draw_transformed(dest, clip, child, xo + cxo, yo + cyo, alpha * what.alpha, child_forward, child_reverse)
+
+
+
+def do_draw_screen(screen_render, full_redraw, swdraw):
+ """
+ Draws the render produced by render_screen to the screen.
+ """
+
+ yoffset = xoffset = 0
+
+ screen_render.is_opaque()
+
+ clip = (xoffset, yoffset, xoffset + screen_render.width, yoffset + screen_render.height)
+ clipper = clippers[0]
+
+ draw(clipper, clip, screen_render, xoffset, yoffset, True)
+
+ cliprect, updates = clipper.compute(full_redraw)
+
+ if cliprect is None:
+ return [ ]
+
+ x, y, _w, _h = cliprect
+
+ dest = swdraw.window.subsurface(cliprect)
+ draw(dest, None, screen_render, -x, -y, True)
+
+ return updates
+
+
+class SWDraw(object):
+ """
+ This uses the software renderer to draw to the screen.
+ """
+
+ def __init__(self):
+ self.display_info = None
+
+ self.reset()
+
+ def reset(self):
+
+ # Should we draw the screen?
+ self.suppressed_blit = False
+
+ # The earliest time at which the next frame can be redrawn.
+ self.next_frame = 0
+
+ # Mouse re-drawing.
+ self.mouse_location = None
+ self.mouse_backing = None
+ self.mouse_backing_pos = None
+ self.mouse_info = None
+
+
+ # Is the mouse currently visible?
+ self.mouse_old_visible = None
+
+ # This is used to cache the surface->texture operation.
+ self.texture_cache = weakref.WeakKeyDictionary()
+
+ # This is used to display video to the screen.
+ self.fullscreen_surface = None
+
+ # Info.
+ self.info = { "renderer" : "sw", "resizable" : False }
+
+ pygame.display.init()
+ renpy.display.interface.post_init()
+
+ if self.display_info is None:
+ self.display_info = pygame.display.Info()
+
+ # The scale factor we use for this display.
+ self.scale_factor = 1.0
+
+ # Should we scale fast, or scale good-looking?
+ self.scale_fast = "RENPY_SCALE_FAST" in os.environ
+
+ # The screen returned to us from pygame.
+ self.screen = None
+
+ # The window that we render into, if not the screen. This has a
+ # 1px border around it iff we're scaling.
+ self.window = None
+
+ def set_mode(self, virtual_size, physical_size, fullscreen):
+
+ # Reset before resize.
+ renpy.display.interface.kill_textures_and_surfaces()
+ self.reset()
+
+ width, height = virtual_size
+
+ # Set up scaling, if necessary.
+ screen_width = self.display_info.current_w
+ screen_height = self.display_info.current_h
+
+ if not fullscreen:
+ screen_height -= 102
+ screen_width -= 102
+
+ scale_factor = min(1.0 * screen_width / width, 1.0 * screen_height / height, 1.0)
+ if "RENPY_SCALE_FACTOR" in os.environ:
+ scale_factor = float(os.environ["RENPY_SCALE_FACTOR"])
+ self.scale_factor = scale_factor
+
+ # Figure out the fullscreen info.
+ if fullscreen:
+ fsflag = pygame.FULLSCREEN
+ else:
+ fsflag = 0
+
+ # If a window exists of the right size and flags, use it. Otherwise,
+ # make our own window.
+ old_screen = pygame.display.get_surface()
+
+ scaled_width = int(width * scale_factor)
+ scaled_height = int(height * scale_factor)
+
+ if ((old_screen is not None) and
+ (old_screen.get_size() == (scaled_width, scaled_height)) and
+ (old_screen.get_flags() & pygame.FULLSCREEN == fsflag)):
+
+ self.screen = old_screen
+
+ else:
+ self.screen = pygame.display.set_mode((scaled_width, scaled_height), fsflag, 32)
+
+ if scale_factor != 1.0:
+ self.window = surface(width, height, True)
+ else:
+ self.window = self.screen
+
+ renpy.display.pgrender.set_rgba_masks()
+
+ # Should we redraw the screen from scratch?
+ self.full_redraw = True
+
+ # The surface used to display fullscreen video.
+ self.fullscreen_surface = self.screen
+
+ # Reset this on a mode change.
+ self.mouse_location = None
+ self.mouse_backing = None
+ self.mouse_backing_pos = None
+ self.mouse_info = None
+
+ return True
+
+ # private
+ def show_mouse(self, pos, info):
+ """
+ Actually shows the mouse.
+ """
+
+ self.mouse_location = pos
+ self.mouse_info = info
+
+ mxo, myo, tex = info
+
+ mx, my = pos
+ mw, mh = tex.get_size()
+
+ bx = mx - mxo
+ by = my - myo
+
+ self.mouse_backing_pos = (bx, by)
+ self.mouse_backing = surface(mw, mh, False)
+ self.mouse_backing.blit(self.window, (0, 0), (bx, by, mw, mh))
+
+ self.screen.blit(tex, (bx, by))
+
+ return bx, by, mw, mh
+
+ # private
+ def hide_mouse(self):
+ """
+ Actually hides the mouse.
+ """
+
+ size = self.mouse_backing.get_size()
+ self.screen.blit(self.mouse_backing, self.mouse_backing_pos)
+
+ rv = self.mouse_backing_pos + size
+
+ self.mouse_backing = None
+ self.mouse_backing_pos = None
+ self.mouse_location = None
+
+ return rv
+
+ # private
+ def draw_mouse(self, show_mouse):
+ """
+ This draws the mouse to the screen, if necessary. It uses the
+ buffer to minimize the amount of the screen that needs to be
+ drawn, and only redraws if the mouse has actually been moved.
+ """
+
+ hardware, x, y, tex = renpy.game.interface.get_mouse_info()
+
+ if self.mouse_old_visible != hardware:
+ pygame.mouse.set_visible(hardware)
+ self.mouse_old_visible = hardware
+
+ # The rest of this is for the software mouse.
+
+ if self.suppressed_blit:
+ return [ ]
+
+ if not show_mouse:
+ tex = None
+
+ info = (x, y, tex)
+ pos = pygame.mouse.get_pos()
+
+ if (pos == self.mouse_location and tex and info == self.mouse_info):
+ return [ ]
+
+ updates = [ ]
+
+ if self.mouse_location:
+ updates.append(self.hide_mouse())
+
+ if tex and pos and renpy.game.interface.focused:
+ updates.append(self.show_mouse(pos, info))
+
+ return updates
+
+ def update_mouse(self):
+ """
+ Draws the mouse, and then updates the screen.
+ """
+
+ updates = self.draw_mouse(True)
+
+ if updates:
+ pygame.display.update(updates)
+
+ def mouse_event(self, ev):
+ x, y = getattr(ev, 'pos', pygame.mouse.get_pos())
+
+ x /= self.scale_factor
+ y /= self.scale_factor
+
+ return x, y
+
+ def get_mouse_pos(self):
+ x, y = pygame.mouse.get_pos()
+
+ x /= self.scale_factor
+ y /= self.scale_factor
+
+ return x, y
+
+
+ def screenshot(self, surftree, fullscreen_video):
+ """
+ Returns a pygame surface containing a screenshot.
+ """
+
+ return self.window
+
+ def should_redraw(self, needs_redraw, first_pass):
+ """
+ Uses the framerate to determine if we can and should redraw.
+ """
+
+ if not needs_redraw:
+ return False
+
+ framerate = renpy.config.framerate
+
+ if framerate is None:
+ return True
+
+ next_frame = self.next_frame
+ now = pygame.time.get_ticks()
+
+ frametime = 1000.0 / framerate
+
+ # Handle timer rollover.
+ if next_frame > now + frametime:
+ next_frame = now
+
+ # It's not yet time for the next frame.
+ if now < next_frame and not first_pass:
+ return False
+
+ # Otherwise, it is. Schedule the next frame.
+ # if next_frame + frametime < now:
+ next_frame = now + frametime
+ # else:
+ # next_frame += frametime
+
+ self.next_frame = next_frame
+
+ return True
+
+
+ def draw_screen(self, surftree, fullscreen_video):
+ """
+ Draws the screen.
+ """
+
+ if not fullscreen_video:
+
+ updates = [ ]
+
+ updates.extend(self.draw_mouse(False))
+
+ damage = do_draw_screen(surftree, self.full_redraw, self)
+
+ if damage:
+ updates.extend(damage)
+
+ self.full_redraw = False
+
+ if self.window is self.screen:
+
+ updates.extend(self.draw_mouse(True))
+ pygame.display.update(updates)
+
+ else:
+
+ if self.scale_fast:
+ pygame.transform.scale(self.window, self.screen.get_size(), self.screen)
+ else:
+ renpy.display.scale.smoothscale(self.window, self.screen.get_size(), self.screen)
+
+ self.draw_mouse(True)
+ pygame.display.flip()
+
+ else:
+ pygame.display.flip()
+ self.full_redraw = True
+
+ self.suppressed_blit = fullscreen_video
+
+
+ def render_to_texture(self, render, alpha):
+
+ rv = surface(render.width, render.height, alpha)
+ draw(rv, None, render, 0, 0, False)
+
+ return rv
+
+ def is_pixel_opaque(self, what, x, y):
+
+ if x < 0 or y < 0 or x >= what.width or y >= what.height:
+ return 0
+
+ for (child, xo, yo, _focus, _main) in what.visible_children:
+ cx = x - xo
+ cy = y - yo
+
+ if what.forward:
+ cx, cy = what.forward.transform(cx, cy)
+
+
+ if isinstance(child, renpy.display.render.Render):
+ if self.is_pixel_opaque(child, x, y):
+ return True
+
+ else:
+ cx = int(cx)
+ cy = int(cy)
+
+ cw, ch = child.get_size()
+ if cx >= cw or cy >= ch:
+ return False
+
+
+
+ if not child.get_masks()[3] or child.get_at((cx, cy))[3]:
+ return True
+
+ return False
+
+
+ def mutated_surface(self, surf):
+ """
+ Called to indicate that the given surface has changed.
+ """
+
+ for i in clippers:
+ i.mutated.add(id(surf))
+
+ if surf in rle_cache:
+ del rle_cache[surf]
+
+
+ def load_texture(self, surf, transient=False):
+ """
+ Creates a texture from the surface. In the software implementation,
+ the only difference between a texture and a surface is that a texture
+ is in the RLE cache.
+ """
+
+ surf = copy_surface(surf)
+ self.mutated_surface(surf)
+
+ if transient:
+ return surf
+
+ if renpy.game.less_memory:
+ return surf
+
+ if surf not in rle_cache:
+ rle_surf = copy_surface(surf)
+ rle_surf.set_alpha(255, pygame.RLEACCEL)
+ self.mutated_surface(rle_surf)
+
+ rle_cache[surf] = rle_surf
+
+ return surf
+
+ def solid_texture(self, w, h, color):
+ """
+ Creates a texture filled to the edges with color.
+ """
+
+ surf = surface(w + 4, h + 4, True)
+ surf.fill(color)
+ self.mutated_surface(surf)
+
+ surf = surf.subsurface((2, 2, w, h))
+
+ self.mutated_surface(surf)
+ return surf
+
+
+ def free_memory(self):
+ """
+ Frees up memory.
+ """
+
+ rle_cache.clear()
+
+ def deinit(self):
+ """
+ Called when we're restarted.
+ """
+
+ renpy.display.render.free_memory()
+
+ return
+
+ def quit(self): #@ReservedAssignment
+ """
+ Shuts down the drawing system.
+ """
+
+ pygame.display.quit()
+
+ return
+
+ def event_peek_sleep(self):
+ """
+ Wait a little bit so the CPU doesn't speed up.
+ """
+
+ time.sleep(.0001)
+
+ def get_physical_size(self):
+ """
+ Return the physical width and height of the screen.
+ """
+ return renpy.config.screen_width, renpy.config.screen_height
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)
+
diff --git a/unrpyc/renpy/display/video.py b/unrpyc/renpy/display/video.py
new file mode 100644
index 0000000..5451e89
--- /dev/null
+++ b/unrpyc/renpy/display/video.py
@@ -0,0 +1,234 @@
+# 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.
+
+import renpy.display
+import renpy.audio
+
+# The movie displayable that's currently being shown on the screen.
+current_movie = None
+
+# True if the movie that is currently displaying is in fullscreen mode,
+# False if it's a smaller size.
+fullscreen = False
+
+# The size of a Movie object that hasn't had an explicit size set.
+default_size = (400, 300)
+
+# The file we allocated the surface for.
+surface_file = None
+
+# The surface to display the movie on, if not fullscreen.
+surface = None
+
+def movie_stop(clear=True, only_fullscreen=False):
+ """
+ Stops the currently playing movie.
+ """
+
+ if (not fullscreen) and only_fullscreen:
+ return
+
+ renpy.audio.music.stop(channel='movie')
+
+
+def movie_start(filename, size=None, loops=0):
+ """
+ This starts a movie playing.
+ """
+
+ if renpy.game.less_updates:
+ return
+
+ global default_size
+
+ if size is not None:
+ default_size = size
+
+ filename = [ filename ]
+
+ if loops == -1:
+ loop = True
+ else:
+ loop = False
+ filename = filename * (loops + 1)
+
+ renpy.audio.music.play(filename, channel='movie', loop=loop)
+
+movie_start_fullscreen = movie_start
+movie_start_displayable = movie_start
+
+def early_interact():
+ """
+ Called early in the interact process, to clear out the fullscreen
+ flag.
+ """
+
+ global fullscreen
+ global current_movie
+
+ fullscreen = True
+ current_movie = None
+
+
+def interact():
+ """
+ This is called each time the screen is redrawn. It helps us decide if
+ the movie should be displayed fullscreen or not.
+ """
+
+ global surface
+ global surface_file
+
+ if not renpy.audio.music.get_playing("movie"):
+ surface = None
+ surface_file = None
+ return False
+
+ if fullscreen:
+ return True
+ else:
+ return False
+
+def get_movie_texture():
+ """
+ Gets a movie texture we can draw to the screen.
+ """
+
+ global surface
+ global surface_file
+
+ playing = renpy.audio.music.get_playing("movie")
+
+ pss = renpy.audio.audio.pss
+
+ if pss:
+ size = pss.movie_size()
+ else:
+ size = (64, 64)
+
+ if (surface is None) or (surface.get_size() != size) or (surface_file != playing):
+ surface = renpy.display.pgrender.surface(size, False)
+ surface_file = playing
+ surface.fill((0, 0, 0, 255))
+
+ tex = None
+
+ if playing is not None:
+ renpy.display.render.mutated_surface(surface)
+ tex = renpy.display.draw.load_texture(surface, True)
+
+ return tex
+
+
+def render_movie(width, height):
+ tex = get_movie_texture()
+
+ if tex is None:
+ return None
+
+ sw, sh = tex.get_size()
+
+ scale = min(1.0 * width / sw, 1.0 * height / sh)
+
+ dw = scale * sw
+ dh = scale * sh
+
+ rv = renpy.display.render.Render(width, height, opaque=True)
+ rv.forward = renpy.display.render.Matrix2D(1.0 / scale, 0.0, 0.0, 1.0 / scale)
+ rv.reverse = renpy.display.render.Matrix2D(scale, 0.0, 0.0, scale)
+ rv.blit(tex, (int((width - dw) / 2), int((height - dh) / 2)))
+
+ return rv
+
+class Movie(renpy.display.core.Displayable):
+ """
+ This is a displayable that shows the current movie.
+ """
+
+ fullscreen = False
+
+ def __init__(self, fps=24, size=None, **properties):
+ """
+ @param fps: The framerate that the movie should be shown at.
+ """
+ super(Movie, self).__init__(**properties)
+ self.size = size
+
+ def render(self, width, height, st, at):
+
+ size = self.size
+
+ if size is None:
+ size = default_size
+
+ width, height = size
+
+ rv = render_movie(width, height)
+
+ if rv is None:
+ rv = renpy.display.render.Render(0, 0)
+
+ # Usually we get redrawn when the frame is ready - but we want
+ # the movie to disappear if it's ended, or if it hasn't started
+ # yet.
+ renpy.display.render.redraw(self, 0.1)
+
+ return rv
+
+
+ def per_interact(self):
+ global fullscreen
+ fullscreen = False
+
+ global current_movie
+ current_movie = self
+
+
+def playing():
+ return renpy.audio.music.get_playing("movie")
+
+def frequent():
+ """
+ Called to update the video playback. Returns true if a video refresh is
+ needed, false otherwise.
+ """
+
+ if not playing():
+ return 0
+
+ pss = renpy.audio.audio.pss
+
+ if pss.needs_alloc():
+
+ if renpy.display.video.fullscreen and renpy.display.draw.fullscreen_surface:
+ surf = renpy.display.draw.fullscreen_surface
+ else:
+ get_movie_texture()
+ surf = renpy.display.scale.real(surface)
+
+ pss.alloc_event(surf)
+
+ rv = pss.refresh_event()
+
+ if rv and current_movie is not None:
+ renpy.display.render.redraw(current_movie, 0)
+
+ return rv
diff --git a/unrpyc/renpy/game.py b/unrpyc/renpy/game.py
new file mode 100644
index 0000000..028da26
--- /dev/null
+++ b/unrpyc/renpy/game.py
@@ -0,0 +1,437 @@
+# 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 module is intended to be used as a singleton object.
+# It's purpose is to store in one global all of the data that would
+# be to annoying to lug around otherwise.
+
+import renpy.display
+
+# The basepath.
+basepath = None
+
+# A list of paths that we search to load things. This is searched for
+# everything that can be loaded, before archives are used.
+searchpath = [ ]
+
+# The options that were read off the command line.
+args = None
+
+# The game's script.
+script = None
+
+# A stack of execution contexts.
+contexts = [ ]
+
+# The interface that the game uses to interact with the user.
+interface = None
+
+# Are we inside lint?
+lint = False
+
+# The RollbackLog that keeps track of changes to the game state
+# and to the store.
+log = None
+
+# Some useful additional information about program execution that
+# can be added to the exception.
+exception_info = ''
+
+# Used to store style information.
+style = None
+
+# The set of statements we've seen in this session.
+seen_session = { }
+
+# The set of statements we've ever seen.
+seen_ever = { }
+
+# True if we're in the first interaction after a rollback or rollforward.
+after_rollback = False
+
+# Code that's run after the init code.
+post_init = [ ]
+
+# Should we attempt to run in a mode that uses less memory?
+less_memory = False
+
+# Should we attempt to run in a mode that minimizes the number
+# of screen updates?
+less_updates = False
+
+# Should we never show the mouse?
+less_mouse = False
+
+# Should we not imagedissiolve?
+less_imagedissolve = False
+
+# The class that's used to hold the persistent data.
+class Persistent(object):
+
+ def __setstate__(self, data):
+ vars(self).update(data)
+
+ def __getstate__(self):
+ return vars(self)
+
+ # Undefined attributes return None.
+ def __getattr__(self, attr):
+ return None
+
+# The persistent data that's kept from session to session
+persistent = Persistent()
+
+class Preferences(renpy.object.Object):
+ """
+ Stores preferences that will one day be persisted.
+ """
+ __version__ = 5
+
+ def after_upgrade(self, version):
+ if version < 1:
+ self.mute_volumes = 0
+ if version < 2:
+ self.using_afm_enable = False
+ if version < 3:
+ self.physical_size = None
+ if version < 4:
+ self.renderer = "auto"
+ self.performance_test = True
+ if version < 5:
+ self.language = None
+
+ def __init__(self):
+ self.fullscreen = False
+ self.skip_unseen = False
+ self.text_cps = 0
+ self.afm_time = 0
+ self.afm_enable = True
+
+ # These will be going away soon.
+ self.sound = True
+ self.music = True
+
+ # 2 - All transitions.
+ # 1 - Only non-default transitions.
+ # 0 - No transitions.
+ self.transitions = 2
+
+ self.skip_after_choices = False
+
+ # Mixer channel info.
+
+ # A map from channel name to the current volume (between 0 and 1).
+ self.volumes = { }
+
+ # True if the channel should not play music. False
+ # otherwise. (Not used anymore.)
+ self.mute = { }
+
+ # Joystick mappings.
+ self.joymap = dict(
+ joy_left="Axis 0.0 Negative",
+ joy_right="Axis 0.0 Positive",
+ joy_up="Axis 0.1 Negative",
+ joy_down="Axis 0.1 Positive",
+ joy_dismiss="Button 0.0")
+
+ # The size of the window, or None if we don't know it yet.
+ self.physical_size = None
+
+ # The graphics renderer we use.
+ self.renderer = "auto"
+
+ # Should we do a performance test on startup?
+ self.performance_test = True
+
+ # The language we use for translations.
+ self.language = None
+
+ def set_volume(self, mixer, volume):
+ self.volumes[mixer] = volume
+
+ def get_volume(self, mixer):
+ return self.volumes.get(mixer, 0)
+
+ def set_mute(self, mixer, mute):
+ self.mute[mixer] = mute
+
+ def get_mute(self, mixer):
+ return self.mute[mixer]
+
+# The current preferences.
+preferences = Preferences()
+
+class RestartContext(Exception):
+ """
+ Restarts the current context. If `label` is given, calls that label
+ in the restarted context.
+ """
+
+ def __init__(self, label):
+ self.label = label
+
+class RestartTopContext(Exception):
+ """
+ Restarts the top context. If `label` is given, calls that label
+ in the restarted context.
+ """
+
+ def __init__(self, label):
+ self.label = label
+
+class FullRestartException(Exception):
+ """
+ An exception of this type forces a hard restart, completely
+ destroying the store and config and so on.
+ """
+
+ def __init__(self, reason="end_game"): # W0231
+ self.reason = reason
+
+class UtterRestartException(Exception):
+ """
+ An exception of this type forces an even harder restart, causing
+ Ren'Py and the script to be reloaded.
+ """
+
+class QuitException(Exception):
+ """
+ An exception of this class will let us force a safe quit, from
+ anywhere in the program.
+
+ `relaunch`
+ If given, the program will run another copy of itself, with the
+ same arguments.
+ """
+
+ def __init__(self, relaunch=False):
+ Exception.__init__(self)
+ self.relaunch = relaunch
+
+class JumpException(Exception):
+ """
+ This should be raised with a label as the only argument. This causes
+ the current statement to terminate, and execution to be transferred
+ to the named label.
+ """
+
+class JumpOutException(Exception):
+ """
+ This should be raised with a label as the only argument. This exits
+ the current context, and then raises a JumpException.
+ """
+
+class CallException(Exception):
+ """
+ Raise this exception to cause the current statement to terminate,
+ and control to be transferred to the named label.
+ """
+
+ def __init__(self, label, args, kwargs):
+ Exception.__init__(self)
+
+ self.label = label
+ self.args = args
+ self.kwargs = kwargs
+
+class EndReplay(Exception):
+ """
+ Raise this exception to end the current replay (the current call to
+ call_replay).
+ """
+
+class ParseErrorException(Exception):
+ """
+ This is raised when a parse error occurs, after it has been
+ reported to the user.
+ """
+
+# A tuple of exceptions that should not be caught by the
+# exception reporting mechanism.
+CONTROL_EXCEPTIONS = (
+ RestartContext,
+ RestartTopContext,
+ FullRestartException,
+ UtterRestartException,
+ QuitException,
+ JumpException,
+ JumpOutException,
+ CallException,
+ EndReplay,
+ ParseErrorException,
+ KeyboardInterrupt,
+ )
+
+
+def context(index=-1):
+ """
+ Return the current execution context, or the context at the
+ given index if one is specified.
+ """
+
+ return contexts[index]
+
+def invoke_in_new_context(callable, *args, **kwargs): #@ReservedAssignment
+ """
+ This pushes the current context, and invokes the given python
+ function in a new context. When that function returns or raises an
+ exception, it removes the new context, and restores the current
+ context.
+
+ Additional arguments and keyword arguments are passed to the
+ callable.
+
+ Please note that the context so created cannot execute renpy
+ code. So exceptions that change the flow of renpy code (like
+ the one created by renpy.jump) cause this context to terminate,
+ and are handled by the next higher context.
+
+ If you want to execute renpy code from the function, you can call
+ it with renpy.call_in_new_context.
+
+ Use this to begin a second interaction with the user while
+ inside an interaction.
+ """
+
+ context = renpy.execution.Context(False, contexts[-1], clear=True)
+ contexts.append(context)
+
+ if renpy.display.interface is not None:
+ renpy.display.interface.enter_context()
+
+ try:
+
+ return callable(*args, **kwargs)
+
+ except renpy.game.JumpOutException as e:
+
+ raise renpy.game.JumpException(e.args[0])
+
+ finally:
+
+ contexts.pop()
+ contexts[-1].do_deferred_rollback()
+
+ if interface.restart_interaction and contexts:
+ contexts[-1].scene_lists.focused = None
+
+
+
+def call_in_new_context(label, *args, **kwargs):
+ """
+ This code creates a new context, and starts executing code from
+ that label in the new context. Rollback is disabled in the
+ new context. (Actually, it will just bring you back to the
+ real context.)
+
+ Use this to begin a second interaction with the user while
+ inside an interaction.
+ """
+
+ context = renpy.execution.Context(False, contexts[-1], clear=True)
+ contexts.append(context)
+
+ if renpy.display.interface is not None:
+ renpy.display.interface.enter_context()
+
+ if args:
+ renpy.store._args = args
+ else:
+ renpy.store._args = None
+
+ if kwargs:
+ renpy.store._kwargs = renpy.python.RevertableDict(kwargs)
+ else:
+ renpy.store._kwargs = None
+
+ try:
+
+ context.goto_label(label)
+ renpy.execution.run_context(False)
+
+ rv = renpy.store._return #@UndefinedVariable
+
+ return rv
+
+ except renpy.game.JumpOutException as e:
+
+ raise renpy.game.JumpException(e.args[0])
+
+ finally:
+
+ contexts.pop()
+ contexts[-1].do_deferred_rollback()
+
+ if interface.restart_interaction and contexts:
+ contexts[-1].scene_lists.focused = None
+
+def call_replay(label, scope={}):
+ """
+ :doc: replay
+
+ Calls a label as a memory.
+
+ Keyword arguments are used to set the initial values of variables in the
+ memory context.
+ """
+
+ renpy.game.log.complete()
+
+ old_log = renpy.game.log
+ renpy.game.log = renpy.python.RollbackLog()
+
+ sb = renpy.python.StoreBackup()
+ renpy.python.clean_stores()
+
+ context = renpy.execution.Context(True)
+ contexts.append(context)
+
+ if renpy.display.interface is not None:
+ renpy.display.interface.enter_context()
+
+ for k, v in scope.items():
+ setattr(renpy.store, k, v)
+
+ renpy.store._in_replay = label
+
+ try:
+
+ context.goto_label("_start_replay")
+ renpy.execution.run_context(False)
+
+ except EndReplay:
+ pass
+
+ finally:
+ contexts.pop()
+ renpy.game.log = old_log
+ sb.restore()
+
+ if interface.restart_interaction and contexts:
+ contexts[-1].scene_lists.focused = None
+
+
+# Type information.
+if False:
+ script = renpy.script.Script()
+ interface = renpy.display.core.Interface()
+ log = renpy.python.RollbackLog()
diff --git a/unrpyc/renpy/log.py b/unrpyc/renpy/log.py
new file mode 100644
index 0000000..37fec9a
--- /dev/null
+++ b/unrpyc/renpy/log.py
@@ -0,0 +1,152 @@
+# 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 module handles the logging of messages to a file.
+
+import os.path
+import codecs
+import traceback
+import platform
+import time
+import tempfile
+
+import renpy
+
+# The file events are logged to.
+log_file = None
+
+class LogFile(object):
+ """
+ This manages one of our logfiles.
+ """
+
+ def __init__(self, name, append=False, developer=False):
+ """
+ `name`
+ The name of the logfile, without the .txt extension.
+ `append`
+ If true, we will append to the logfile. If false, we will truncate
+ it to an empty file the first time we write to it.
+ `developer`
+ If true, nothing happens if config.developer is not set to True.
+ """
+
+ self.name = name
+ self.append = append
+ self.developer = developer
+ self.file = None
+
+ # File-like attributes.
+ self.softspace = 0
+ self.newlines = None
+
+ # Should we emulate file's write method? We do so if this is True.
+ self.raw_write = False
+
+ def open(self): #@ReservedAssignment
+
+ if self.file:
+ return True
+
+ if self.developer and not renpy.config.developer:
+ return False
+
+ if not renpy.config.log_enable:
+ return False
+
+ try:
+ base = os.environ.get("RENPY_LOG_BASE", renpy.config.basedir)
+ fn = os.path.join(base, self.name + ".txt")
+
+ altfn = os.path.join(tempfile.gettempdir(), "renpy-" + self.name + ".txt")
+
+
+ if renpy.android:
+ print("Logging to", fn)
+
+ if self.append:
+ mode = "a"
+ else:
+ mode = "w"
+
+ try:
+ self.file = codecs.open(fn, mode, "utf-8")
+ except:
+ self.file = codecs.open(altfn, mode, "utf-8")
+
+ if self.append:
+ self.write('')
+ self.write('=' * 78)
+ self.write('')
+
+ self.write("%s", time.ctime())
+ self.write("%s", platform.platform())
+ self.write("%s", renpy.version)
+ self.write("%s %s", renpy.config.name, renpy.config.version)
+ self.write("")
+
+ return True
+
+ except:
+ return False
+
+ def write(self, s, *args):
+ """
+ Formats `s` with args, and writes it to the logfile.
+ """
+
+ if self.open():
+
+ if not self.raw_write:
+ s = s % args
+ s += "\n"
+
+ if not isinstance(s, str):
+ s = s.decode("latin-1")
+
+ s = s.replace("\n", "\r\n")
+
+ self.file.write(s)
+ self.file.flush()
+
+ def exception(self):
+ """
+ Writes the exception to the logfile.
+ """
+
+ self.raw_write = True
+ traceback.print_exc(None, self)
+ self.raw_write = False
+
+# A map from the log name to a log object.
+log_cache = { }
+
+def open(name, append=False, developer=False): #@ReservedAssignment
+ rv = log_cache.get(name, None)
+
+ if rv is None:
+ rv = LogFile(name, append=append, developer=developer)
+ log_cache[name] = rv
+
+ return rv
+
+
+
diff --git a/unrpyc/renpy/object.py b/unrpyc/renpy/object.py
new file mode 100644
index 0000000..c595e8c
--- /dev/null
+++ b/unrpyc/renpy/object.py
@@ -0,0 +1,60 @@
+# 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.
+
+class Object(object):
+ """
+ Our own base class. Contains methods to simplify serialization.
+ """
+
+ __version__ = 0
+
+ nosave = [ ]
+
+ def __getstate__(self):
+ rv = vars(self).copy()
+
+ for f in self.nosave:
+ if f in rv:
+ del rv[f]
+
+
+ rv["__version__"] = self.__version__
+
+ return rv
+
+
+ # None, to prevent this from being called when unnecessary.
+ after_setstate = None
+
+ def __setstate__(self, new_dict):
+
+ version = new_dict.pop("__version__", 0)
+
+ self.__dict__.update(new_dict)
+
+ if version != self.__version__:
+ self.after_upgrade(version) # E1101
+
+ if self.after_setstate:
+ self.after_setstate() # E1102
+
+# We don't handle slots with this mechanism, since the call to vars should
+# throw an error.