Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 655b2aeb58 |
-237
@@ -1,240 +1,3 @@
|
||||
New in 4.8
|
||||
----------
|
||||
|
||||
A new system was implemented to manage widget focus. This new system
|
||||
allows the focus to be moved from widget to widget using the keyboard,
|
||||
without requiring the keymouse behavior be used. Because of this new
|
||||
system, the keymouse behavior is now a no-op.
|
||||
|
||||
To simplify the code and make every widget compatible with the new
|
||||
focus code, a number of widgets were reimplemented in terms of
|
||||
buttons. This includes the ui.menu widget, which is the widget that is
|
||||
used when presenting the user with choices. Menu buttons are of the
|
||||
new style style.menu_choice_button, while their labels remain of style
|
||||
style.menu_choice. Background images and padding can now be styled
|
||||
onto these menu buttons.
|
||||
|
||||
A number of new image manipulators have been made available in the
|
||||
new im package. These are objects that can load an image (im.Image),
|
||||
crop an image (im.Crop), scale images (im.Scale), rotozoom images
|
||||
(im.Rotozoom) and composite images (im.Composite). The latter is much
|
||||
more flexible than the previous image composition system, as it now
|
||||
supports placing images at arbitrary locations on an arbitrarily big
|
||||
canvas. Image (by itself) is now a function that creates the
|
||||
appropriate image manipulators, retaining compatibility with its
|
||||
behavior in previous releases.
|
||||
|
||||
The image cache is now based on image size, rather than number of
|
||||
images. This prevents a large number of small images from filling up
|
||||
the cache.
|
||||
|
||||
We now load images before starting the timer for a transition. This
|
||||
prevents us from popping into the middle of a transition because we
|
||||
spent some time loading images. This behavior can be controlled by
|
||||
config.load_before_transition.
|
||||
|
||||
The ui.imagemap widget has been rewritten in terms of ui.imagebuttons
|
||||
and im.Crops. Some styles have changed names as a result.
|
||||
|
||||
Ren'Py now keeps track of the images that have been shown to the
|
||||
user. This is the basis of this release's new extra, gallery.rpy,
|
||||
which manages a gallery of unlockable CG.
|
||||
|
||||
The audio system has been rewritten, and a number of low-level audio
|
||||
functions have been exposed in the new audio file. These functions
|
||||
control audio directly, but do not save the state of audio in a save
|
||||
file. The high-level music functions have been rewritten and placed in
|
||||
the music.rpy file, allowing a sufficently adventurous game author to
|
||||
change the way music is played.
|
||||
|
||||
Two changes have been made to the renpy.music_start() function in the
|
||||
high-level sound api. The startpos argument was removed (as it was a
|
||||
pain to support), and a new fadeout argument was added, which lets the
|
||||
fadeout time be controlled for each new song that is played.
|
||||
|
||||
The low-level sound api now lets us do the following things:
|
||||
|
||||
[list][*] Play up to eight sound effects at once.
|
||||
[*] Cancel a playing sound effect.
|
||||
[*] Pause and unpause the playing music.
|
||||
[/list]
|
||||
|
||||
There is now a new, state that a widget can be in. Along with hover,
|
||||
idle, and activate, a widget can now be insensitive. This is the state
|
||||
that all non-focused widgets are in. Having the new insensitive state
|
||||
means that we can eliminate the various disabled_button styles, so we
|
||||
did.
|
||||
|
||||
In general, in this release, many styles have changed names.
|
||||
|
||||
In prior versions of Ren'Py, the bar widget could respond to
|
||||
clicks. This was never used, and won't work well with the
|
||||
keyboard focus control system. So, it's been eliminated.
|
||||
|
||||
Now, if the label "enter_game_menu" exists, it is called when entering
|
||||
the game menu. It's expected that the main purpose of this label will
|
||||
be to play music when in the game menu. Using the default music
|
||||
system, the code to do this would look like:
|
||||
|
||||
[code]
|
||||
label enter_game_menu:
|
||||
$ renpy.music_start("game_menu_music.mid")
|
||||
return
|
||||
[/code]
|
||||
|
||||
A number of bugs were fixed, including bugs with the style system, and
|
||||
with animation refresh.
|
||||
|
||||
Tab characters are now expanded to tab stop at eight spaces, rather
|
||||
than to a fixed spacing of eight spaces. Old code using tabs may need
|
||||
to be reformatted.
|
||||
|
||||
The 'h' key now hides the windows on the screen. This means that all
|
||||
Ren'Py functionality is now available through the keyboard, at least
|
||||
for people with a 1-button mouse. (cough ... Mac ... cough)
|
||||
|
||||
Finally, setting the environment variable RENPY_DISABLE_FULLSCREEN to
|
||||
a non-empty value will disable fullscreen support, for those people
|
||||
(that person?) who had it crash their systems.
|
||||
|
||||
|
||||
New in 4.7.2
|
||||
------------
|
||||
|
||||
This release should be compatiable with the .rpyc and .save files
|
||||
produced by 4.7.1.
|
||||
|
||||
The new readback extra caused save files to reach a size and/or
|
||||
complexity that caused errors in cPickle on Windows, which in turn
|
||||
lead to a repeatable crash of Ren'Py, and left a corrupt save file in
|
||||
the saves directory that prevented further saving and loading of
|
||||
games. This has been addressed in two ways. The first is the use of
|
||||
pickle (as opposed to cPickle) throught Ren'Py, a change that may slow
|
||||
down the engine a little but should ensure correctness. The second is
|
||||
that we now catch the errors produced by corrupt files, and (if
|
||||
config.debug is not set) proceed by ignoring that file.
|
||||
|
||||
There is now a new extra, readback.rpy. This implements readback, in
|
||||
addition to, or instead of, rollback. Readback is limited in a number
|
||||
of ways, insofar as it only shows text without changing pictures or
|
||||
other things. As part of implementing readback, we added a new
|
||||
function, say, that is called when the user gives dialogue consisting
|
||||
of a pair of strings.
|
||||
|
||||
There are a number of changes that improve support of the Mac OS X
|
||||
platform. The foremost among them is that we ship a new file,
|
||||
run_game.pyw. This is identical to run_game.py, but is necessary for
|
||||
the game to run in a graphical environment on the Mac. (It also will
|
||||
run the game without a console window on Windows, if you have the
|
||||
appropriate dependencies.) Ren'Py still requires that PyObjC and
|
||||
pygame are installed on a OS X 10.3 box before it can run.
|
||||
|
||||
The way font line spacing is computed was changed in this release, in
|
||||
the hope of making it consistent on all three platforms. We now ignore
|
||||
font linesize hints, which seem to be computed incorrectly on some
|
||||
platforms, and instead set the line spacing to be equal to the ascent
|
||||
and descent of the font. The user can increase or decrease the line
|
||||
spacing by setting the new line_spacing text property. The old
|
||||
line_height_fudge property is now ignored. The style.rpy common file
|
||||
was updated to take account of this change.
|
||||
|
||||
To aid the user in finding the source of error messages, we now report
|
||||
the name of the file being parsed when an error occurs during the
|
||||
parse phase.
|
||||
|
||||
The tutorial has now been renamed "The Ren'Py Reference Manual", as
|
||||
it's really more of a poorly-organized comprehensive reference then a
|
||||
tutorial. This is in the hope that the Ren'Py user community will one
|
||||
day write a more reasonable tutorial. A number of errors in the
|
||||
reference were corrected.
|
||||
|
||||
The functions used in keymaps can now return values, which are
|
||||
returned to the ui.interact() that called them. This was needed
|
||||
to support readback.
|
||||
|
||||
LICENSE.txt has been updated to include the names and URLs of software
|
||||
that a windows build of Ren'Py depends on, the licenses of which you
|
||||
will be required to comply with when releasing a Ren'Py game.
|
||||
|
||||
New in 4.7.1
|
||||
------------
|
||||
|
||||
Added a new tool, dump_text.py/.exe, that dumps all of the text from a
|
||||
Ren'Py script file. It uses a heuristic approach that is imperfect,
|
||||
but is hopefully accurate enough to be useful for purposes like
|
||||
spell-checking a script. The text is dumped into the file text.txt in
|
||||
the current directory, and on windows that file is then displayed to
|
||||
the user. By default all of the rpy files in the game directory are
|
||||
displayed, but supplying a filename parameter can change that.
|
||||
|
||||
Fixed a bug that was preventing parser error messages from being
|
||||
reported on Windows.
|
||||
|
||||
Fixed a bug in Render.subsurface that lead to weird effects when doing
|
||||
an irisin or irisout on a sufficently complicated scene. See
|
||||
http://lemmasoft.renai.us/forums/viewtopic.php?p=4612#4612 for the
|
||||
discussion.
|
||||
|
||||
New in 4.7
|
||||
----------
|
||||
|
||||
The demo was updated to show the new features in 4.7.
|
||||
|
||||
The big new feature in this release is text tags. Text tags is a
|
||||
markup language that allows text properties to be changed inside a
|
||||
single unit of text. It lets you emphasize individual words by making
|
||||
them bold, bigger, or a different color. Rather than explaining it
|
||||
fully here, let me point you to the new "Text Tags" section of the
|
||||
documentation.
|
||||
|
||||
There were two changes that were made to Ren'Py to support text
|
||||
tags. The first was that interpolation now quotes the interpolated
|
||||
text, making it impossible to introduce a text tag via interpolation.
|
||||
|
||||
The second change is that renpy.input() and friends now take as
|
||||
arguments strings giving allowed and excluded characters. The default
|
||||
is to exclude the '{' and '}' characters, meaning that it's impossible
|
||||
for a user to input a text tag.
|
||||
|
||||
Finally, this release features even more defensive programming
|
||||
involving sound. Now, if the mixer does not initialize, no further
|
||||
sound operations will be performed.
|
||||
|
||||
New in 4.6.2
|
||||
------------
|
||||
|
||||
4.6.2 was never officially released, as it didn't cure all of the
|
||||
sound woes, and didn't have much else in the way of new features.
|
||||
|
||||
Added a speed test, which tests the speed of the dissolve transition
|
||||
on a user's system. This test can be accessed by typing 'S' during the
|
||||
demo (that's shift+the 's' key).
|
||||
|
||||
Fixed a bug with sound on systems with no soundcard in them. (Also,
|
||||
put in a note to remind myself not to re-introduce that bug.)
|
||||
|
||||
Fixed a bug in which imagemaps would not redraw properly.
|
||||
|
||||
New in 4.6.1
|
||||
------------
|
||||
|
||||
Fixed a major memory leak in the rendering code. This leak was
|
||||
rendering 4.6 and 4.5 unusable on smaller systems.
|
||||
|
||||
Executed 250,000 statements (well, 5 statements in a loop, while
|
||||
skipping), and ensured that there was no memory leak during the
|
||||
execution of these statements.
|
||||
|
||||
Added a memory profiler. You can see it by supplying the --leak option
|
||||
to run_game.exe or .py, but don't expect to understand it.
|
||||
|
||||
Changed Dissolve so that it tries to use compositing when it can. This
|
||||
means that it only draws to the screen once... which is a little bit
|
||||
of a speedup on a really expensive operation.
|
||||
|
||||
Fixed a bug that was preventing predictive image loading from
|
||||
working.
|
||||
|
||||
New in 4.6
|
||||
----------
|
||||
|
||||
|
||||
-25
@@ -20,28 +20,3 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
Please note that the above license only applies to Ren'Py
|
||||
proper. A binary distribution of Ren'Py contains a number of other
|
||||
packages, each with their own licenses, which you may be bound to
|
||||
comply with when distributing a Ren'Py game.
|
||||
|
||||
These packages may include:
|
||||
|
||||
Python (Python License)
|
||||
http://www.python.org
|
||||
|
||||
Pygame (GNU LGPL)
|
||||
http://www.pygame.org
|
||||
|
||||
SDL (GNU LGPL)
|
||||
http://www.libsdl.org
|
||||
|
||||
SDL_mixer (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_mixer
|
||||
|
||||
SDL_ttf (GNU LGPL)
|
||||
http://www.libsdl.org/projects/SDL_ttf
|
||||
|
||||
ctypes (MIT)
|
||||
http://starship.python.net/crew/theller/ctypes/
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
Have a new form of imagemap that uses a color image to distinguish
|
||||
hotspots.
|
||||
|
||||
Keyd compiles.
|
||||
Have a new form of imagemap that uses a color image to distinguish hotspots.
|
||||
+1
-1
@@ -26,6 +26,6 @@ sys.argv[1:] = [ 'py2exe' ]
|
||||
|
||||
setup(name="RenPy",
|
||||
windows=programs,
|
||||
console=[ "archiver.py", "add_from.py", "console.py", "dump_text.py" ],
|
||||
console=[ "archiver.py", "add_from.py", "console.py" ],
|
||||
zipfile='lib/renpy.zip',
|
||||
)
|
||||
|
||||
+7
-13
@@ -113,11 +113,11 @@ init -500:
|
||||
|
||||
style = type
|
||||
|
||||
if selected and not disabled:
|
||||
if selected:
|
||||
style += "_selected"
|
||||
|
||||
if disabled:
|
||||
clicked = None
|
||||
style += "_disabled"
|
||||
|
||||
style = style + "_button"
|
||||
text_style = style + "_text"
|
||||
@@ -302,7 +302,7 @@ init -500:
|
||||
ui.fixed()
|
||||
|
||||
ui.window(style='gm_nav_window')
|
||||
ui.vbox(focus='gm_nav')
|
||||
ui.vbox()
|
||||
|
||||
for key, label, target, enabled in library.game_menu:
|
||||
|
||||
@@ -311,7 +311,7 @@ init -500:
|
||||
|
||||
if not eval(enabled):
|
||||
disabled = True
|
||||
clicked = None
|
||||
clicked = ui.returns(None)
|
||||
|
||||
_button_factory(label, "gm_nav", selected=(key==selected),
|
||||
disabled=disabled, clicked=clicked)
|
||||
@@ -331,8 +331,8 @@ init -500:
|
||||
clicked=ui.returns(("return", (name, False)))
|
||||
enable_hover = True
|
||||
else:
|
||||
clicked = None
|
||||
enable_hover = True
|
||||
clicked=None
|
||||
enable_hover = False
|
||||
|
||||
ui.button(style='file_picker_entry',
|
||||
clicked=clicked,
|
||||
@@ -382,10 +382,7 @@ init -500:
|
||||
fpi = 0
|
||||
|
||||
if newest:
|
||||
fpi = (int(newest) - 1) // library.file_page_length * library.file_page_length
|
||||
|
||||
if fpi < 0:
|
||||
fpi = 0
|
||||
fpi = int(newest) // library.file_page_length * library.file_page_length
|
||||
|
||||
|
||||
# The length of a half-page of files.
|
||||
@@ -517,9 +514,6 @@ label _enter_game_menu:
|
||||
if library.enter_transition:
|
||||
$ renpy.transition(library.enter_transition)
|
||||
|
||||
if renpy.has_label("enter_game_menu"):
|
||||
call enter_game_menu
|
||||
|
||||
return
|
||||
|
||||
# Entry points from the game into menu-space.
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
# This file contains code to manage the playing of music. It's here,
|
||||
# in an rpy file, because we now optionally let the user override all
|
||||
# of this to implement his or her own music system, using calls to
|
||||
# various functions found in audio.
|
||||
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
config.debug_sound = True
|
||||
|
||||
# This becomes renpy.music_start.
|
||||
def music_start(filename, loops=-1, fadeout=None):
|
||||
"""
|
||||
This starts music playing. If a music track is already
|
||||
playing, this pauses that music track in favor of this
|
||||
one.
|
||||
|
||||
@param filename: The file that the music will be played from.
|
||||
This is relative to the game directory, and must be a real
|
||||
file (so it cannot be stored in an archive).
|
||||
|
||||
@param loops: The number of times the music will loop
|
||||
after it finishes playing for the first time. This is
|
||||
made somewhat less accurate by rollback and loading. If
|
||||
this number is less than zero, the song will loop
|
||||
forever.
|
||||
|
||||
@param fadeout: If this parameter is not None, it is
|
||||
interpreted as a time in seconds, which gives how long
|
||||
the fade will take.
|
||||
"""
|
||||
|
||||
if loops >= 0:
|
||||
loops += 1
|
||||
|
||||
ctx = renpy.context()
|
||||
ctx._music_name = filename
|
||||
ctx._music_loops = loops
|
||||
|
||||
if fadeout and audio.music_enabled() and not config.skipping:
|
||||
audio.music_fadeout(fadeout)
|
||||
|
||||
def music_stop():
|
||||
"""
|
||||
This stops the currently playing music track.
|
||||
"""
|
||||
|
||||
ctx = renpy.context()
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
renpy.music_start = music_start
|
||||
renpy.music_stop = music_stop
|
||||
|
||||
# This is called once for each interaction, to ensure that the
|
||||
# music is appropriate for that interaction.
|
||||
def music_interact():
|
||||
|
||||
if not audio.music_enabled():
|
||||
return
|
||||
|
||||
ctx = renpy.context()
|
||||
|
||||
if not hasattr(ctx, '_music_name'):
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
playing, queued = audio.music_filenames()
|
||||
|
||||
# If music is disabled, ensure that it is stopped.
|
||||
if not _preferences.music:
|
||||
if playing:
|
||||
audio.music_stop()
|
||||
|
||||
return
|
||||
|
||||
# If we do not match what is playing, stop what's currently
|
||||
# playing and get ready to play something else.
|
||||
if ctx._music_name != playing:
|
||||
|
||||
# If we're not playing anything, immediately start the
|
||||
# new track by calling music_end_event.
|
||||
if not playing:
|
||||
config.music_end_event()
|
||||
|
||||
# Otherwise, we will start a fade to the new music if no
|
||||
# such fade is already in progress, and we're not skipping.
|
||||
elif not audio.music_fading() and not config.skipping:
|
||||
audio.music_fadeout(config.fade_music)
|
||||
|
||||
# This is called whenever a track of music ends, or also from the
|
||||
# above when we want to start a new track when nothing else is
|
||||
# playing.
|
||||
def music_end_event():
|
||||
|
||||
if not _preferences.music:
|
||||
return
|
||||
|
||||
ctx = renpy.context()
|
||||
playing, queued = audio.music_filenames()
|
||||
|
||||
if not hasattr(ctx, '_music_name'):
|
||||
ctx._music_name = None
|
||||
ctx._music_loops = None
|
||||
|
||||
if not ctx._music_name:
|
||||
return
|
||||
|
||||
if not playing and ctx._music_loops:
|
||||
ctx._music_loops -= 1
|
||||
audio.music_play(ctx._music_name)
|
||||
|
||||
if not queued and ctx._music_loops:
|
||||
ctx._music_loops -= 1
|
||||
audio.music_queue(ctx._music_name)
|
||||
|
||||
config.music_interact = music_interact
|
||||
config.music_end_event = music_end_event
|
||||
|
||||
|
||||
|
||||
+44
-22
@@ -36,9 +36,6 @@ init -1000:
|
||||
style.default.antialias = True
|
||||
style.default.size = 22
|
||||
style.default.color = (255, 255, 255, 255)
|
||||
style.default.bold = False
|
||||
style.default.italic = False
|
||||
style.default.underline = False
|
||||
style.default.drop_shadow = (1, 1)
|
||||
style.default.drop_shadow_color = (0, 0, 0, 128)
|
||||
style.default.minwidth = 0
|
||||
@@ -46,8 +43,13 @@ init -1000:
|
||||
style.default.text_y_fudge = 0
|
||||
style.default.first_indent = 0
|
||||
style.default.rest_indent = 0
|
||||
style.default.line_spacing = 0
|
||||
|
||||
# Change this if you're not using Vera 22.
|
||||
if renpy.windows():
|
||||
style.default.line_height_fudge = -4
|
||||
else:
|
||||
style.default.line_height_fudge = 0
|
||||
|
||||
# Window properties.
|
||||
style.default.background = None
|
||||
style.default.xpadding = 0
|
||||
@@ -66,7 +68,8 @@ init -1000:
|
||||
style.default.yanchor = 'top'
|
||||
|
||||
# Sound properties.
|
||||
style.default.sound = None
|
||||
style.default.hover_sound = None
|
||||
style.default.activate_sound = None
|
||||
|
||||
# The base style for the large windows.
|
||||
style.create('window', 'default',
|
||||
@@ -104,8 +107,6 @@ init -1000:
|
||||
the label of dialogue. The label is used to
|
||||
indicate who is saying something.""")
|
||||
|
||||
style.say_label.bold = True
|
||||
|
||||
style.create('say_dialogue', 'default',
|
||||
"""(text) The style that is used by default for
|
||||
the text of dialogue.""")
|
||||
@@ -122,18 +123,13 @@ init -1000:
|
||||
# Styles that are used for menus.
|
||||
|
||||
style.create('menu', 'default',
|
||||
"(position) The style that is used for the vbox containing a menu.")
|
||||
"(sound, position) The style that is used for menus themselves.")
|
||||
|
||||
style.create('menu_caption', 'default',
|
||||
"(text) The style that is used to render a menu caption.")
|
||||
|
||||
style.create('menu_choice', 'default',
|
||||
"""(text, hover) The style that is used to render
|
||||
the text of a menu choice.""")
|
||||
|
||||
style.create('menu_choice_button', 'default',
|
||||
"""(window, hover, sound) The style that is used
|
||||
to render the button containing a menu choice.""")
|
||||
"""(text, hover, sound) The style that is used to render a menu choice.""")
|
||||
|
||||
style.menu_choice.hover_color = (255, 255, 0, 255) # yellow
|
||||
style.menu_choice.activate_color = (255, 255, 0, 255) # yellow
|
||||
@@ -179,12 +175,9 @@ init -1000:
|
||||
|
||||
# Styles that are used by imagemaps
|
||||
style.create('imagemap', 'image_placement',
|
||||
'(position) The style that is used for imagemaps.')
|
||||
'(sound, position) The style that is used for imagemaps.')
|
||||
|
||||
style.create('imagemap_button', 'default',
|
||||
'(window, sound, hover) The style that is used for buttons inside imagemaps.')
|
||||
|
||||
# Styles that are used by imagebutttons.
|
||||
# Style that is used by imagebutttons.
|
||||
style.create('image_button', 'default',
|
||||
'(window, sound, hover) The default style used for image buttons.')
|
||||
|
||||
@@ -208,7 +201,6 @@ init -1000:
|
||||
style.button_text.color = dark_cyan
|
||||
style.button_text.hover_color = bright_cyan
|
||||
style.button_text.activate_color = bright_cyan
|
||||
style.button_text.insensitive_color = (192, 192, 192, 255)
|
||||
style.button_text.drop_shadow = (2, 2)
|
||||
|
||||
# Selected button.
|
||||
@@ -222,12 +214,30 @@ init -1000:
|
||||
style.selected_button_text.hover_color = bright_red
|
||||
style.selected_button_text.activate_color = bright_red
|
||||
|
||||
# Disabled button.
|
||||
|
||||
style.create('disabled_button', 'button',
|
||||
'(window, hover) The style that is used for a disabled button.')
|
||||
|
||||
style.disabled_button.hover_sound = None
|
||||
style.disabled_button.activate_sound = None
|
||||
|
||||
style.create('disabled_button_text', 'button_text',
|
||||
'(text, hover) The style that is used for the label of a disabled button.')
|
||||
|
||||
style.disabled_button_text.color = (128, 128, 128, 255)
|
||||
style.disabled_button_text.hover_color = (128, 128, 128, 255)
|
||||
style.disabled_button_text.activate_color = (128, 128, 128, 255)
|
||||
|
||||
|
||||
# Bar.
|
||||
style.create('bar', 'default',
|
||||
'(bar) The style that is used by default for bars.')
|
||||
|
||||
style.bar.left_bar = Solid(bright_cyan)
|
||||
style.bar.right_bar = Solid((0, 0, 0, 128))
|
||||
style.bar.left_gutter = 0
|
||||
style.bar.right_gutter = 0
|
||||
|
||||
# Styles that are used when laying out the main menu.
|
||||
style.create('mm_root_window', 'default',
|
||||
@@ -276,6 +286,13 @@ init -1000:
|
||||
|
||||
style.create('gm_nav_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_disabled_button', 'disabled_button',
|
||||
'(window, hover) The style of a disabled game menu navigation button.')
|
||||
|
||||
style.create('gm_nav_disabled_button_text', 'disabled_button_text',
|
||||
'(text, hover) The style of the text of a disabled game menu navigation button.')
|
||||
|
||||
|
||||
style.create('file_picker_window', 'default',
|
||||
'(window, position) A window containing the file picker that is used to choose slots for loading and saving.')
|
||||
@@ -295,6 +312,11 @@ init -1000:
|
||||
'(window, hover) The style that is used for enabled file picker navigation buttons.')
|
||||
style.create('file_picker_nav_button_text', 'button_text',
|
||||
'(text) The style that is used for the label of enabled file picker navigation buttons.')
|
||||
|
||||
style.create('file_picker_nav_disabled_button', 'disabled_button',
|
||||
'(window, hover) The style that is used for disabled file picker navigation buttons.')
|
||||
style.create('file_picker_nav_disabled_button_text', 'disabled_button_text',
|
||||
'(text) The style that is used for the label of disabled file picker navigation buttons.')
|
||||
|
||||
style.create('file_picker_entry', 'button',
|
||||
'(window, hover) The style that is used for each of the slots in the file picker.')
|
||||
@@ -305,7 +327,7 @@ init -1000:
|
||||
style.file_picker_entry.xminimum = 400
|
||||
style.file_picker_entry.ymargin = 2
|
||||
|
||||
style.file_picker_entry.background = Solid((255, 255, 255, 255))
|
||||
style.file_picker_entry.idle_background = Solid((255, 255, 255, 255))
|
||||
style.file_picker_entry.hover_background = Solid((255, 255, 192, 255))
|
||||
style.file_picker_entry.activate_background = Solid((255, 255, 192, 255))
|
||||
|
||||
@@ -313,7 +335,7 @@ init -1000:
|
||||
'(text) A base style for all text that is displayed in the file picker.')
|
||||
|
||||
style.file_picker_text.size = 18
|
||||
style.file_picker_text.color = dark_cyan
|
||||
style.file_picker_text.idle_color = dark_cyan
|
||||
style.file_picker_text.hover_color = bright_cyan
|
||||
|
||||
style.create('file_picker_new', 'file_picker_text',
|
||||
|
||||
+9
-53
@@ -2,8 +2,7 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
@@ -21,21 +20,18 @@ import sys
|
||||
# Extra things used for distribution.
|
||||
import encodings.utf_8
|
||||
import encodings.zlib_codec
|
||||
import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
def main():
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
@@ -55,9 +51,6 @@ def main():
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--leak', dest='leak', action='store_true', default=False,
|
||||
help='When the game exits, dumps a profile of memory usage.')
|
||||
|
||||
options, args = op.parse_args()
|
||||
|
||||
if options.python:
|
||||
@@ -113,45 +106,8 @@ def main():
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def memory_profile():
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
print
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
objs = gc.get_objects()
|
||||
|
||||
c = { } # count
|
||||
dead_renders = 0
|
||||
|
||||
for i in objs:
|
||||
t = type(i)
|
||||
c[t] = c.get(t, 0) + 1
|
||||
|
||||
if isinstance(i, renpy.display.render.Render):
|
||||
if i.dead:
|
||||
dead_renders += 1
|
||||
|
||||
|
||||
results = [ (count, ty) for ty, count in c.iteritems() ]
|
||||
results.sort()
|
||||
|
||||
for count, ty in results:
|
||||
print count, str(ty)
|
||||
|
||||
if dead_renders:
|
||||
print
|
||||
print "*** found", dead_renders, "dead Renders. ***"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+4
-83
@@ -124,14 +124,9 @@ label start:
|
||||
scene washington with fade
|
||||
show eileen vhappy with dissolve
|
||||
|
||||
# Store the current version of Ren'Py into a variable, so we can
|
||||
# interpolate it into the next line.
|
||||
$ version = renpy.version()
|
||||
|
||||
# Display a line of dialogue. In this case, we manually specify
|
||||
# who's saying the line of dialogue. We also interpolate in the
|
||||
# version of Ren'Py we're using.
|
||||
"Girl" "Hi, and welcome to the %(version)s demo program."
|
||||
# who's saying the line of dialoge.
|
||||
"Girl" "Hi, and welcome to the Ren'Py 4 demo program."
|
||||
|
||||
# This instantly replaces the very happy picture of Eileen with
|
||||
# one showing her merely happy. It demonstrates how the show
|
||||
@@ -636,47 +631,6 @@ label ending:
|
||||
|
||||
$ renpy.full_restart()
|
||||
|
||||
|
||||
label speedtest:
|
||||
|
||||
with None
|
||||
scene whitehouse
|
||||
show eileen happy
|
||||
with dissolve
|
||||
|
||||
e "Okay, I'm going to run the speedtest on your system."
|
||||
|
||||
e "I'll only be testing the performance of the dissolve
|
||||
transition. It taxes your system the most, as it needs to
|
||||
redraw the entire screen each frame."
|
||||
|
||||
$ frames = config.frames
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with Dissolve(5.0)
|
||||
|
||||
$ frames = config.frames - frames
|
||||
$ fps = frames / 5.0
|
||||
|
||||
e "Well, your system displayed %(frames)d frames in five
|
||||
seconds. That's %(fps).1f fps."
|
||||
|
||||
e "Remember, this is the worst-case speed, as usually we can just
|
||||
draw the parts of the screen that have changed."
|
||||
|
||||
e "Thanks for viewing the secret speed test."
|
||||
|
||||
return
|
||||
|
||||
# Setup the secret key for the speedtest.
|
||||
init:
|
||||
python:
|
||||
config.keymap['speedtest'] = [ 'S' ]
|
||||
config.underlay.append(renpy.Keymap(speedtest=renpy.curried_call_in_new_context('speedtest')))
|
||||
|
||||
|
||||
init:
|
||||
|
||||
# This is just some example code to show the ui functions in
|
||||
@@ -839,9 +793,6 @@ label whatsnew:
|
||||
"I'd like to start with 4.6.":
|
||||
jump whatsnew46
|
||||
|
||||
"I'd like to start with 4.7.":
|
||||
jump whatsnew47
|
||||
|
||||
label whatsnew45:
|
||||
|
||||
show washington
|
||||
@@ -972,38 +923,6 @@ label whatsnew46:
|
||||
e "A few more obscure features involving things like overlays and
|
||||
activated widgets round out the 4.6 release."
|
||||
|
||||
label whatsnew47:
|
||||
|
||||
e "Ren'Py 4.7 brought with it a total rewrite of the way text is
|
||||
rendered to the screen."
|
||||
|
||||
e "It introduced text tags, which let a script writer control how
|
||||
text is shown on the screen."
|
||||
|
||||
e "Text tags can make text {b}bold{/b}, {i}italic{/i}, or even
|
||||
{u}underlined{/u}."
|
||||
|
||||
e "They can make the font size {size=+12}bigger{/size} or
|
||||
{size=-8}smaller{/size}."
|
||||
|
||||
e "They can even change the
|
||||
{color=#f00}color{/color}
|
||||
{color=#ff0}of{/color}
|
||||
{color=#0f0}the{/color}
|
||||
{color=#0ff}text{/color}."
|
||||
|
||||
e "We also added bold, italic, and underline style properties, which can
|
||||
be styled onto any text."
|
||||
|
||||
e "Used with care, text tags can enhance {b}your{/b} game."
|
||||
|
||||
e "{u}Used{/u} with {i}abandon,{/i} they {b}can{/b} make {b}your{/b}
|
||||
game {color=#333}hard{/color} {color=#888}to{/color} {color=#ccc}read{/color}."
|
||||
|
||||
e "With great power comes great responsibility, after all."
|
||||
|
||||
e "And we want to give you all the power you need."
|
||||
|
||||
label whatsnewend:
|
||||
|
||||
e "Anyway, now that you've heard about some of the new features, is there anything
|
||||
@@ -1011,4 +930,6 @@ label whatsnewend:
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+1
-3
@@ -97,7 +97,7 @@ def main():
|
||||
|
||||
doc_files = [
|
||||
'example.html',
|
||||
'reference.html',
|
||||
'tutorial.html',
|
||||
'style.css',
|
||||
]
|
||||
|
||||
@@ -123,11 +123,9 @@ def main():
|
||||
cp("README_RENPY.txt")
|
||||
cp("archive_images.bat")
|
||||
cp("run_game.py", license=license)
|
||||
copy_file("run_game.py", target + "/run_game.pyw", license=license)
|
||||
cp("archiver.py", license=license)
|
||||
# cp("build_exe.py", license=license)
|
||||
cp("add_from.py", license=license)
|
||||
cp("dump_text.py", license=license)
|
||||
cp("renpy-mode.el")
|
||||
|
||||
|
||||
|
||||
+5
-5
@@ -1,9 +1,9 @@
|
||||
all:: reference.html example.html
|
||||
all:: tutorial.html example.html
|
||||
|
||||
reference.html: reference.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py reference.xml > reference.hi.xml
|
||||
xsltproc stylesheet.xslt reference.hi.xml > reference.html
|
||||
cp reference.html style.css ~/ab/website/renpy/devel/doc
|
||||
tutorial.html: tutorial.xml preprocess.py stylesheet.xslt style.css styles.xml
|
||||
python preprocess.py tutorial.xml > tutorial.hi.xml
|
||||
xsltproc stylesheet.xslt tutorial.hi.xml > tutorial.html
|
||||
cp tutorial.html style.css ~/ab/website/renpy/devel/doc
|
||||
|
||||
|
||||
example.html: example.xml preprocess.py stylesheet.xslt style.css styles.xml ../demo2/script.rpy
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
@@ -133,14 +132,6 @@ def main():
|
||||
s = f.read()
|
||||
f.close()
|
||||
|
||||
os.chdir("..")
|
||||
|
||||
try:
|
||||
renpy.main.main("dump_styles")
|
||||
except "foo":
|
||||
pass
|
||||
|
||||
os.chdir("doc")
|
||||
|
||||
s = re.sub(r"<!-- func (\S+) -->", function, s)
|
||||
s = re.sub(r"<!-- include (\S+) -->", include, s)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<doc>
|
||||
<title>The Ren'Py Reference Manual</title>
|
||||
<title>Writing Visual Novels with Ren'Py</title>
|
||||
<subtitle>The Ren'Py Tutorial</subtitle>
|
||||
|
||||
<p>
|
||||
<b>Last Updated:</b> <!-- date -->
|
||||
@@ -358,8 +359,7 @@ e "Starting today, I'll be living here."
|
||||
<p>
|
||||
Finally, the string in a dialogue is subject to interpolation of
|
||||
variables. A string variable can be interpolated with %(name)s,
|
||||
while a number requires %(name)d. The interpolated value is quoted
|
||||
so that a text tag cannot be interpolated in by mistake. For example:
|
||||
while a number requires %(name)d. For example:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
@@ -1722,16 +1722,14 @@ e "Pleased to meet you, %(name)s."
|
||||
|
||||
<!-- func renpy.watch -->
|
||||
|
||||
<!-- func renpy.loadable -->
|
||||
<!-- func renpy.windows -->
|
||||
|
||||
<!-- func renpy.exists -->
|
||||
|
||||
<!-- func renpy.context -->
|
||||
|
||||
<!-- func renpy.clear_game_runtime -->
|
||||
|
||||
<!-- func renpy.get_game_runtime -->
|
||||
|
||||
|
||||
<!-- func color -->
|
||||
|
||||
<p>
|
||||
@@ -2022,11 +2020,10 @@ init:
|
||||
stdout (wherever that may go to).
|
||||
</var>
|
||||
|
||||
<var name="config.image_cache_size" value="8">
|
||||
|
||||
This is used to set the size of the image cache, as a multiple of the
|
||||
screen size. This is mostly a suggestion, as if Ren'Py ever needs to
|
||||
display more than this amount of images at once, it will cache
|
||||
<var name="config.image_cache_size" value="10">
|
||||
This is the number of images that can be in the image cache at
|
||||
once. This is mostly a suggestion, as if Ren'Py ever needs to
|
||||
display more than this number of images at once, it will cache
|
||||
them all. But it will stop predictively loading images when the
|
||||
cache is filled beyond this, and it will try to drop images from
|
||||
the cache until it shrinks below this size. If this is too
|
||||
@@ -2050,11 +2047,6 @@ screen size. This is mostly a suggestion, as if Ren'Py ever needs to
|
||||
the image cache change.
|
||||
</var>
|
||||
|
||||
<var name="config.load_before_transitions" value="True">
|
||||
If True, the start of transitions will be delayed until the images
|
||||
used by said transitions have finished loading.
|
||||
</var>
|
||||
|
||||
<var name="config.allow_skipping" value="True">
|
||||
If set to False, the user is not able to skip over the text of the
|
||||
game.
|
||||
@@ -2087,9 +2079,9 @@ load scripts, as scripts will be loaded before it can be set.
|
||||
is slightly increased.
|
||||
</var>
|
||||
|
||||
<var name="config.focus_crossrange_penalty" value="1024">
|
||||
This is the amount of penalty to apply to moves perpendicular to the
|
||||
selected direction of motion, when moving focus with the keyboard.
|
||||
<var name="config.keymouse_distance" value="5">
|
||||
This gives the number of pixels that the mouse is moved every
|
||||
1/20th of a second when the keymouse is being used.
|
||||
</var>
|
||||
|
||||
<var name="config.sound_sample_rate" value="44100">
|
||||
@@ -2279,10 +2271,10 @@ performed when exiting the game menu.
|
||||
init:
|
||||
|
||||
# Selected menu choices should be yellow and solid.
|
||||
$ style.menu_choice.hover_color = (255, 255, 0, 255)
|
||||
$ style.menu_choice_selected.color = (255, 255, 0, 255)
|
||||
|
||||
# Unselected menu choices should be cyan and translucent.
|
||||
$ style.menu_choice.idle_color = (0, 255, 255, 128)
|
||||
$ style.menu_choice_unselected = (0, 255, 255, 128)
|
||||
</example>
|
||||
|
||||
|
||||
@@ -2310,25 +2302,12 @@ init:
|
||||
|
||||
<prop name="color">
|
||||
The color in which the text will be displayed on the screen, as an
|
||||
RGBA tuple. Due to a limitation of pygame, currently the alpha is
|
||||
ignored. Example: (255, 255, 255, 255)
|
||||
RGBA tuple. Example: (255, 255, 255, 255)
|
||||
</prop>
|
||||
|
||||
<prop name="bold">
|
||||
If True, then this text will be rendered in bold.
|
||||
</prop>
|
||||
|
||||
<prop name="italic">
|
||||
If True, then this text will be rendered in italics.
|
||||
</prop>
|
||||
|
||||
<prop name="underline">
|
||||
If true, then this text will be rendered with an underline.
|
||||
</prop>
|
||||
|
||||
<prop name="antialias">
|
||||
If True, the text will be antialiased. Otherwise, it will be left
|
||||
jagged.
|
||||
jagged. Example: True
|
||||
</prop>
|
||||
|
||||
<prop name="drop_shadow">
|
||||
@@ -2351,10 +2330,13 @@ If true, then this text will be rendered with an underline.
|
||||
at least this many pixels long.
|
||||
</prop>
|
||||
|
||||
<prop name="line_spacing">
|
||||
This is used to increase or decrease the spacing between lines of
|
||||
text by a constant number of pixels. A positive value increases, while
|
||||
a negative value decreases the spacing.
|
||||
<prop name="line_height_fudge">
|
||||
On some platforms (Windows), the height of a line of text isn't
|
||||
reported correctly. This can lead to an overly-large spacing
|
||||
between lines. This is a number that is added to the reported line
|
||||
spacing. If negative, the lines will be pushed closer
|
||||
together. This is very platform-dependent, so you may want to
|
||||
condition it on the result of renpy.windows().
|
||||
</prop>
|
||||
|
||||
<prop name="text_y_fudge">
|
||||
@@ -2568,7 +2550,12 @@ top_padding and bottom_padding to the same value.
|
||||
|
||||
<p>
|
||||
The ui.bar() widget has a few properties that are specific to
|
||||
it, that control the look of the bars.
|
||||
it. These properties control the look of the bars, and the size of
|
||||
the gutters on either side of a clickable widget. The gutters are
|
||||
empty space on the left and right of a clickable bar, and serve to
|
||||
make it easy to set the maximum and minimum value of the bar
|
||||
widget. Clicking on the left gutter sets the minimum value, while the
|
||||
right gutter sets the maximum value.
|
||||
</p>
|
||||
|
||||
<prop name="left_bar">
|
||||
@@ -2583,22 +2570,29 @@ Displayable needs to be one that can change its size, such as a Solid
|
||||
or a Frame.
|
||||
</prop>
|
||||
|
||||
<prop name="left_gutter">
|
||||
The size of the left gutter of a clickable bar, in pixels.
|
||||
</prop>
|
||||
|
||||
<prop name="right_gutter">
|
||||
The size of the left gutter of a clickable bar, in pixels.
|
||||
</prop>
|
||||
|
||||
|
||||
|
||||
<h4>Hovering</h4>
|
||||
|
||||
<p>
|
||||
In Ren'Py, buttons and their contents support the idea of
|
||||
hovering: using different sets of properties for different states
|
||||
of the widget. If the widget cannot be given focus, it is
|
||||
considered to be insensitive. If the widget has been selected by
|
||||
the user, it is considered to be activated. Otherwise, if the
|
||||
widget has focus, then that widget is hovered, else it is idle. On
|
||||
these widgets, Ren'Py will look up a property prefixed with
|
||||
"insensitive_", "activate_", "hover_", or "idle_" (as appropriate)
|
||||
in a style, before looking up an unprefixed property. If neither
|
||||
form is found on a style, the process is repeated again on the
|
||||
parent style. (The search pattern for background on a hovered
|
||||
button goes: button.hover_background, button.background,
|
||||
In Ren'Py, buttons and their contents support the idea of hovering:
|
||||
using different sets of properties for different states of the
|
||||
widget. If the widget has been selected, it is considered to be
|
||||
activated. Otherwise, if the mouse is over the widget, then that widget
|
||||
is hovered, else it is idle. On these widgets, Ren'Py will look up
|
||||
a property prefixed with "activate_", "hover_", "idle_", (as appropriate) in a
|
||||
style, before looking up an unprefixed property. If neither form
|
||||
is found on a style, the process is repeated again on the parent
|
||||
style. (The search pattern for background on a hovered button
|
||||
goes: button.hover_background, button.background,
|
||||
default.hover_background, default.background.)
|
||||
</p>
|
||||
|
||||
@@ -2651,7 +2645,7 @@ init:
|
||||
init:
|
||||
$ style.button.color = (128, 255, 128, 255)
|
||||
$ style.button.xpos = 0
|
||||
$ style.button.idle_background = \
|
||||
$ style.button_idled.background = \
|
||||
renpy.Frame(renpy.Image("button_idled.png"),
|
||||
xborder=10, yborder=10)
|
||||
$ style.button_hover.background = \
|
||||
@@ -2708,95 +2702,6 @@ init:
|
||||
|
||||
</dl>
|
||||
|
||||
<h3>Text Tags</h3>
|
||||
|
||||
<p>
|
||||
Styles are useful if you want to change the look of entire blocks
|
||||
of text in the game. But what if you only want to change the look of a
|
||||
small amount of text, or if you want to only change the look of entire
|
||||
blocks of text. Text tags let you change the look of text within a
|
||||
single text widget.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Text tags work wherever text can be placed. This means that they can
|
||||
be used inside text widgets, text buttons, and menu and say
|
||||
statements. But please note that say statement protect from
|
||||
subsituting in a text tag, as that would most likely be an
|
||||
error. Generally, though, if Ren'Py shows text to the user, that text
|
||||
can be formatted through the use of text tags.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As an aside, let me point out that if you're using a text tag on every
|
||||
line of a script, or even many lines, you're probably doing something
|
||||
wrong. That's what styles are for.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Text tags start with a left brace, and continue to the matching right
|
||||
brace. We chose to use braces because they do not require quoting in
|
||||
HTML or BBCode. An open text tag may take a parameter, and then continues until
|
||||
a closing tag, which is a tag that has a slash immediately after the
|
||||
opening brace. Please note that text tags are sensitive to both case
|
||||
and whitespace.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Some example tags are {b}, {i}, {color=#f88}, {size=+16}. The matching
|
||||
closing tags are {/b}, {i}, {/color}, and {/size}. Tags must be
|
||||
properly nested: "{b}{i}bold + italic{/i}{/b}" is okay,
|
||||
but "{b}{i}this is wrong{/b}{/i}". Right now, improper nesting won't
|
||||
cause an error but it probably won't do what you want. All open tags
|
||||
must be closed by the end of the string.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you, for some reason, want to include a left brace, just write two
|
||||
left braces ({{) instead.
|
||||
</p>
|
||||
|
||||
<h4>Supported Text Tags</h4>
|
||||
|
||||
<p>The following are supported text tags:</p>
|
||||
|
||||
<ul>
|
||||
|
||||
<li>{b}text{/b} makes the enclosed text bold.</li>
|
||||
|
||||
<li>{i}text{/i} makes the enclosed text italic.</li>
|
||||
|
||||
<li>{u}text{/u} makes the enclosed text underlined.</li>
|
||||
|
||||
<li>{plain}text{/plain} makes the enclosed text plain, which means that
|
||||
it is not bold, italic, or underlined.</li>
|
||||
|
||||
<li>{size=<i>spec</i>}text{/size} changes the size of the text. The
|
||||
supplied spec may be a number, in which case it is the number of
|
||||
pixels high that the text will be. If it is a number preceded by a
|
||||
plus (like "+10"), it means to increase the size by that number of
|
||||
pixels, while if it is preceded by a minus it means to decrease.
|
||||
</li>
|
||||
|
||||
<li>{color=<i>spec</i>}text{/color} changes the color of the text. The
|
||||
color may be a hex triple or a hex sextuple, optionally preceded by a
|
||||
hash mark. (This is the same format that color() accepts.)</li>
|
||||
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Here's an example of a say statement that uses many of the text
|
||||
tags. If you actually use this many text tags in a real game, you'll
|
||||
probably drive your users mad.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
"Using text tags, we can make text {size=+12}bigger{/size} or
|
||||
{size=-8}smaller{/size}. We can make it {b}bold{/b}, {i}italic{/i},
|
||||
or {u}underlined{/u}. We can even change its {color=#f88}color{/color}."
|
||||
</example>
|
||||
|
||||
|
||||
<h3>Obfuscating your Game</h3>
|
||||
|
||||
<p>
|
||||
@@ -2823,13 +2728,13 @@ probably drive your users mad.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Images can be archived using the archiver.py or archiver.exe
|
||||
Images can be archived using the archive.py or archive.exe
|
||||
programs that ship as part of Ren'Py. To use this, one can change
|
||||
into the game directory and run a command like:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<kbd>../archiver.exe images *.png *.jpg</kbd>
|
||||
<kbd>../archive.exe images *.png *.jpg</kbd>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -2950,26 +2855,31 @@ Ren'Py, and as of version 4.5 is as follows:
|
||||
|
||||
<example>
|
||||
keymap = dict(
|
||||
|
||||
# Bindings present almost everywhere, unless explicitly
|
||||
# disabled.
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mousedown_3' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Focus.
|
||||
focus_left = [ 'K_LEFT' ],
|
||||
focus_right = [ 'K_RIGHT' ],
|
||||
focus_up = [ 'K_UP' ],
|
||||
focus_down = [ 'K_DOWN' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
keymouse_right = [ 'K_RIGHT' ],
|
||||
keymouse_up = [ 'K_UP' ],
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouseup_1' ],
|
||||
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
|
||||
menu_keyup = [ 'K_UP' ],
|
||||
menu_keydown = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
@@ -2977,6 +2887,12 @@ keymap = dict(
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouseup_1' ],
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
@@ -3064,18 +2980,6 @@ changed by a call to ui.layer(), followed by a matching call to
|
||||
ui.close() when done adding widgets to the new layer.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Widgets can be added to a focus group by supplying the name of the
|
||||
focus group as the focus parameter to a ui widget. If the same focus
|
||||
group exists on two interactions, Ren'Py will ensure that if the nth
|
||||
widget is selected in the first interaction, the nth widget will be
|
||||
given focus at the start of the second interaction. If no widget is
|
||||
given focus in this way, then Ren'Py looks for one that has had the
|
||||
default=True argument supplied to it. If it finds such a widget, then
|
||||
it focuses it. Otherwise, it uses fallback rules to determine if a
|
||||
widget should be given focus.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following are the functions available in the ui module, which is
|
||||
automatically present in the game namespace.
|
||||
@@ -3119,6 +3023,8 @@ automatically present in the game namespace.
|
||||
|
||||
<!-- func ui.pausebehavior -->
|
||||
|
||||
<!-- func ui.keymousebehavior -->
|
||||
|
||||
<h4>Functions</h4>
|
||||
|
||||
<!-- func ui.interact -->
|
||||
@@ -3146,112 +3052,6 @@ relying on this function.)
|
||||
</p>
|
||||
</function>
|
||||
|
||||
<h3>Image Manipulators</h3>
|
||||
|
||||
<p>
|
||||
Image manipulators are objects that can return images. Every image
|
||||
manipulator can be used as a widget, but the opposite is not
|
||||
true. Image manipulators are used to implement the Image function,
|
||||
but constructing them directly can give you finer control.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When an image manipulator requires another as input, the second image
|
||||
manipulator can be specified in any of a number of ways. The first way
|
||||
is as a string, which is turned into an im.Image. The second is a
|
||||
tuple, which is turned into an im.Composite using the rules for tuples
|
||||
given in Image. The third is that an image manipulator can be
|
||||
specified directly, in which case it is passed through unchanged.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Widgets may not be used as arguments to image manipulators. This is
|
||||
because image manipulators are very efficent, caching the computed
|
||||
images on their first load.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The image manipulators are:
|
||||
</p>
|
||||
|
||||
<!-- func im.Image -->
|
||||
|
||||
<!-- func im.Crop -->
|
||||
|
||||
<!-- func im.Composite -->
|
||||
|
||||
<!-- func im.Scale -->
|
||||
|
||||
<!-- func im.Rotozoom -->
|
||||
|
||||
<h3>Low-Level Audio Functions</h3>
|
||||
|
||||
<p>
|
||||
These functions manipulate Ren'Py audio subsystem directly. Unlike the
|
||||
high-level music functions, these functions do not manipulate
|
||||
persistent state. As a result, calls to these functions will not
|
||||
persistent across sessions.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Still, these functions are useful to allow access to behavior that
|
||||
is not yet exposed through the high-level interface. They are also
|
||||
useful for a person who wants to change the high-level music or sound
|
||||
interfaces. So, we've documented them here.
|
||||
</p>
|
||||
|
||||
<h4>Sound Functions</h4>
|
||||
|
||||
<!-- func audio.sound_enabled -->
|
||||
|
||||
<!-- func audio.sound_play -->
|
||||
|
||||
<!-- func audio.sound_stop -->
|
||||
|
||||
<h4>Music Functions</h4>
|
||||
|
||||
<p>
|
||||
The low-level music system deals with two music files at once, a
|
||||
playing file and a queued file. When the playing file finishes
|
||||
playing, the queued file is begun, and queued file becomes
|
||||
empty.
|
||||
</p>
|
||||
|
||||
<!-- func audio.music_enabled -->
|
||||
|
||||
<!-- func audio.music_play -->
|
||||
|
||||
<!-- func audio.music_queue -->
|
||||
|
||||
<!-- func audio.music_stop -->
|
||||
|
||||
<!-- func audio.music_fadeout -->
|
||||
|
||||
<!-- func audio.music_fading -->
|
||||
|
||||
<!-- func audio.music_filenames -->
|
||||
|
||||
<!-- func audio.music_pause -->
|
||||
|
||||
<!-- func audio.music_unpause -->
|
||||
|
||||
<h4>Music Callbacks</h4>
|
||||
|
||||
<var name="config.music_interact" value="...">
|
||||
This callback is called (without any arguments) at the start (or
|
||||
restart) of each interaction. It's intended to allow the system to
|
||||
restore music after a context change, or to implement changes in
|
||||
the music that occured before that interaction.
|
||||
</var>
|
||||
|
||||
<var name="config.music_end_event" value="...">
|
||||
This callback is called (without any arguments) when a track has
|
||||
finished playing. If a second track has been queued, it will have
|
||||
started before this callback is called. Usually, this callback is
|
||||
used to queue up another track when one ends. It can also be used
|
||||
to start the music playing again after a fadeout.
|
||||
</var>
|
||||
|
||||
|
||||
<h3>Function Index</h3>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
init:
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
|
||||
label main_menu:
|
||||
label start:
|
||||
|
||||
$ renpy.renpy.style.write_docs("doc/styles.xml")
|
||||
$ raise "foo"
|
||||
|
||||
$ style._write_docs("doc/styles.xml")
|
||||
"Dumped style."
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
# This program dumps all the text found in the script to the file text.txt.
|
||||
# If on windows, it also tries to show text.txt to the user.
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
import renpy
|
||||
|
||||
def process_block(block, out):
|
||||
|
||||
for fn, ln, text, child in block:
|
||||
|
||||
if text.startswith("$") or text.startswith("python"):
|
||||
continue
|
||||
|
||||
if text.startswith("init"):
|
||||
continue
|
||||
|
||||
if text.startswith("if") or text.startswith("while"):
|
||||
process_block(child, out)
|
||||
continue
|
||||
|
||||
for m in re.finditer(r'"((?:[^\\"]+|\\.)+)"|' +
|
||||
r"'((?:[^\\']+|\\.)+)'", text):
|
||||
|
||||
s = m.group(1) or m.group(2)
|
||||
|
||||
s = re.sub(r'\s+', ' ', s)
|
||||
s = re.sub(r'\\.', ' ', s)
|
||||
|
||||
s = re.sub(r'\{.*?\}', '', s)
|
||||
|
||||
print >>out, s.encode('utf-8')
|
||||
print >>out
|
||||
|
||||
process_block(child, out)
|
||||
|
||||
|
||||
def process(fn, out):
|
||||
|
||||
block = renpy.parser.group_logical_lines(renpy.parser.list_logical_lines(fn))
|
||||
|
||||
process_block(block, out)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
pattern = "game/*.rpy"
|
||||
|
||||
if len(sys.argv) >= 2:
|
||||
pattern = sys.argv[1]
|
||||
|
||||
files = glob.glob(pattern)
|
||||
files = [ i for i in files if not i.startswith("common/") ]
|
||||
|
||||
out = file("text.txt", "w")
|
||||
out.write(codecs.BOM_UTF8)
|
||||
|
||||
for fn in files:
|
||||
process(fn, out)
|
||||
|
||||
out.close()
|
||||
|
||||
try:
|
||||
os.startfile('text.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,35 +0,0 @@
|
||||
# This file adds a number of buttons to the lower-right hand corner of
|
||||
# the screen. Three of these buttons jump to the game menu, which
|
||||
# giving quick access to Load, Save, and Prefs. The fourth button
|
||||
# toggles skipping, to make that more convenient.
|
||||
|
||||
init:
|
||||
|
||||
# Give us some space on the right side of the screen.
|
||||
$ style.window.right_margin = 100
|
||||
|
||||
python:
|
||||
|
||||
def toggle_skipping():
|
||||
config.skipping = not config.skipping
|
||||
|
||||
def button_game_menu():
|
||||
|
||||
# to save typing
|
||||
ccinc = renpy.curried_call_in_new_context
|
||||
|
||||
ui.vbox(xpos=0.98, ypos=0.98, xanchor='right', yanchor='bottom')
|
||||
ui.textbutton("Skip", clicked=toggle_skipping)
|
||||
ui.textbutton("Save", clicked=ccinc("_game_menu_save"))
|
||||
ui.textbutton("Load", clicked=ccinc("_game_menu_load"))
|
||||
ui.textbutton("Prefs", clicked=ccinc("_game_menu_preferences"))
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(button_game_menu)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
# This extra implements an image gallery, complete with automatic
|
||||
# unlocking of the images that have been shown to the user. The images
|
||||
# are divided into pages, with a fixed number of images on each page.
|
||||
|
||||
# Right now, this is configured to used the images found in the demo.
|
||||
# The images are repeated quite a bit, so that we can get four pages
|
||||
# of them. You probably wouldn't do that if you were using this in a
|
||||
# real game.
|
||||
|
||||
# To see this in action, drop it into the game directory of the
|
||||
# demo.
|
||||
|
||||
# Configuration.
|
||||
init:
|
||||
|
||||
python hide:
|
||||
|
||||
|
||||
# The number of columns and rows of images to show in the
|
||||
# gallery.
|
||||
store.gallery_cols = 3
|
||||
store.gallery_rows = 4
|
||||
|
||||
# The size that each image should be scaled to, and that
|
||||
# thumbnails should be.
|
||||
store.gallery_width = 160
|
||||
store.gallery_height = 120
|
||||
|
||||
# The contents of each of the page. Each of these is a list of
|
||||
# tuples with the first element of the tuple being the image
|
||||
# filename and the second element being the name of the image
|
||||
# that will unlock the image with this filename. (That is,
|
||||
# the name that is used in show or scene statements, as a
|
||||
# string.)
|
||||
#
|
||||
# When displaying an image as a thumbnail, this code first
|
||||
# looks for the file thumbnail_<filename>. If that file
|
||||
# exists, it should be a gallery_width x gallery_height
|
||||
# thumbnail. Otherwise, a thumbnail is automatically
|
||||
# generated, but it may screw up the aspect ratio of the
|
||||
# image.
|
||||
#
|
||||
# You probably want to create thumbnails for most images, to
|
||||
# limit memory consumption.
|
||||
page1 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page2 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
( "carillon.jpg", "carillon" ),
|
||||
]
|
||||
|
||||
page3 = [
|
||||
( "whitehouse.jpg", "whitehouse" ),
|
||||
( "washington.jpg", "washington" ),
|
||||
]
|
||||
|
||||
page4 = [
|
||||
( "9a_happy.png", "eileen happy" ),
|
||||
( "9a_vhappy.png", "eileen vhappy" ),
|
||||
( "9a_concerned.png", "eileen concerned" ),
|
||||
]
|
||||
|
||||
# This is the actual list of gallery pages. It's a list
|
||||
# of tuples, with the first element being the name of the
|
||||
# page, the second being the contents of the page (one of
|
||||
# the lists created above), and the third image being the
|
||||
# image used as the background of the page.
|
||||
store.gallery_pages = [
|
||||
("Backgrounds 1", page1, "washington.jpg"),
|
||||
("Backgrounds 2", page2, "whitehouse.jpg"),
|
||||
("Backgrounds 3", page3, "carillon.jpg"),
|
||||
("Character Art", page4, "washington.jpg"),
|
||||
]
|
||||
|
||||
# A window containing the gallery page buttons.
|
||||
style.create('gallery_pages', 'default')
|
||||
style.gallery_pages.xpos = 0.99
|
||||
style.gallery_pages.xanchor='right'
|
||||
style.gallery_pages.ypos = 0.02
|
||||
style.gallery_pages.yanchor = 'top'
|
||||
|
||||
# A button that links to a gallery page.
|
||||
style.create('gallery_page_button', 'button')
|
||||
style.create('gallery_page_button_text', 'button_text')
|
||||
|
||||
# A button that returns us to from whence we came.
|
||||
style.create('gallery_return_button', 'button')
|
||||
style.create('gallery_return_button_text', 'button_text')
|
||||
|
||||
style.gallery_return_button.xpos = 0.99
|
||||
style.gallery_return_button.xanchor='right'
|
||||
style.gallery_return_button.ypos = 0.98
|
||||
style.gallery_return_button.yanchor = 'bottom'
|
||||
|
||||
|
||||
# The grid containing the gallery image buttons.
|
||||
style.create('gallery_grid', 'default')
|
||||
|
||||
# The style of the buttons in the gallery.
|
||||
style.create('gallery_button', 'default')
|
||||
|
||||
# Right now, the backgrounds are all solids, but in a more
|
||||
# professional version, the insensitive background would
|
||||
# probably be a placeholder image that indicates that a
|
||||
# picture has yet to be unlocked.
|
||||
style.gallery_button.insensitive_background = Solid((192, 192, 192, 255))
|
||||
style.gallery_button.idle_background = Solid((255, 255, 255, 255))
|
||||
style.gallery_button.hover_background = Solid((255, 255, 192, 255))
|
||||
|
||||
style.gallery_button.left_margin = 5
|
||||
style.gallery_button.right_margin = 5
|
||||
style.gallery_button.top_margin = 5
|
||||
style.gallery_button.bottom_margin = 5
|
||||
|
||||
style.gallery_button.left_padding = 5
|
||||
style.gallery_button.right_padding = 5
|
||||
style.gallery_button.top_padding = 5
|
||||
style.gallery_button.bottom_padding = 5
|
||||
|
||||
# The style of the images in the buttons in the gallery.
|
||||
style.create('gallery_button_image', 'default')
|
||||
|
||||
# The style of the images that are being shown to the user.
|
||||
style.create('gallery_image', 'image_placement')
|
||||
|
||||
# The transition used when switching gallery pages.
|
||||
store.gallery_transition = Dissolve(0.5)
|
||||
|
||||
python:
|
||||
|
||||
# The function that actually manages the display of the image
|
||||
# gallery.
|
||||
def gallery():
|
||||
|
||||
page = 0
|
||||
|
||||
while True:
|
||||
|
||||
images = gallery_pages[page][1]
|
||||
ui.image(gallery_pages[page][2])
|
||||
|
||||
# Show the names of the various gallery pages.
|
||||
|
||||
ui.window(style='gallery_pages')
|
||||
ui.vbox(focus="gallery_pages")
|
||||
|
||||
for i in range(0, len(gallery_pages)):
|
||||
if i == page:
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(("page", i))
|
||||
|
||||
ui.textbutton(gallery_pages[i][0],
|
||||
style='gallery_page_button',
|
||||
text_style='gallery_page_button_text',
|
||||
clicked=clicked)
|
||||
ui.close()
|
||||
|
||||
# Show the return button.
|
||||
ui.textbutton('Return',
|
||||
style='gallery_return_button',
|
||||
text_style='gallery_return_button_text',
|
||||
clicked=ui.returns(("return", None)))
|
||||
|
||||
# Show the grid for this page.
|
||||
ui.grid(gallery_cols, gallery_rows, style='gallery_grid')
|
||||
|
||||
# For each grid cell.
|
||||
for i in range(0, gallery_cols * gallery_rows):
|
||||
|
||||
# Fill empty space with nulls.
|
||||
if i >= len(images):
|
||||
ui.null()
|
||||
continue
|
||||
|
||||
# Otherwise, get the filename and spec and see if
|
||||
# we've unlocked it.
|
||||
filename, spec = images[i]
|
||||
|
||||
if spec:
|
||||
spec = spec.split()
|
||||
|
||||
if spec and tuple(spec) not in persistent._seen_images:
|
||||
filename = None
|
||||
clicked = None
|
||||
else:
|
||||
clicked = ui.returns(('show', filename))
|
||||
|
||||
# Create the button, containing the appropriate
|
||||
# image or a null if we haven't unlocked it yet.
|
||||
|
||||
ui.button(style='gallery_button', clicked=clicked)
|
||||
|
||||
if not filename:
|
||||
ui.null(width=gallery_width, height=gallery_height)
|
||||
else:
|
||||
|
||||
if renpy.loadable("thumbnail_" + filename):
|
||||
ui.image("thumbnail_" + filename,
|
||||
style="gallery_button_image")
|
||||
else:
|
||||
ui.add(im.Scale(filename,
|
||||
gallery_width,
|
||||
gallery_height))
|
||||
|
||||
ui.close()
|
||||
|
||||
# Interact with the user.
|
||||
renpy.transition(gallery_transition)
|
||||
cmd, arg = ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
# Process the user's commands.
|
||||
if cmd == "show":
|
||||
ui.add(Solid((0, 0, 0, 255)))
|
||||
ui.image(arg)
|
||||
ui.saybehavior()
|
||||
renpy.transition(gallery_transition)
|
||||
ui.interact(suppress_overlay=True, suppress_underlay=True)
|
||||
|
||||
if cmd == "page":
|
||||
page = arg
|
||||
|
||||
if cmd == "return":
|
||||
renpy.transition(gallery_transition)
|
||||
return
|
||||
|
||||
|
||||
library.main_menu.insert(2, ( "CG Gallery", "gallery"))
|
||||
|
||||
|
||||
label gallery:
|
||||
|
||||
$ gallery()
|
||||
|
||||
jump _main_menu
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,247 +0,0 @@
|
||||
# This is the annoying readback mode. I implemented this because
|
||||
# people asked for it, not because I like it. I'd prefer you use
|
||||
# rollback, which is better in every way.
|
||||
|
||||
# To use readback, drop this file into your game directory. Readback
|
||||
# will take over when rollback no longer works, so you'll also need to
|
||||
# either reduce config.hard_rollback_limit or set
|
||||
# config.rollback_enabled to false.
|
||||
|
||||
# init:
|
||||
# $ config.hard_rollback_limit = 1
|
||||
|
||||
# Readback works by replacing the default Character and Menu objects
|
||||
# with ones that record what is said in a readback buffer. The user
|
||||
# can then go back, and what has been said will be shown to them
|
||||
# again. Images and the like will not change... if you want that, use
|
||||
# rollback. Heck, use rollback anyway. :-P
|
||||
|
||||
# The number of lines of readback can be changed by assigning an
|
||||
# integer to the readback_limit variable. But leaving it None will
|
||||
# leave the entire game in the buffer. As this is actually fairly
|
||||
# memory-efficent, you should probably leave it as None.
|
||||
|
||||
# Readback uses two styles, readback_dialogue and
|
||||
# readback_thought. Change them to change the color and look of
|
||||
# read-back text.
|
||||
|
||||
# If you want to add your own message, as narration, to the readback
|
||||
# buffer, you can do it by calling the readback function or having
|
||||
# the readback character say it:
|
||||
|
||||
# $ readback('A message for readbackers only.')
|
||||
# readback "A similar message."
|
||||
|
||||
# Note: When using Readback, you can no longer use a string for a
|
||||
# character's name. All dialogue must be routed through Character
|
||||
# objects, if it is to show up in the readback buffer. This limitation
|
||||
# will be fixed in a future version of Ren'Py.
|
||||
|
||||
init -100:
|
||||
|
||||
python:
|
||||
|
||||
# The limit of the number of readbacks to keep. None means no limit.
|
||||
readback_limit = None
|
||||
|
||||
# Set this to true to print out the contents of the readback
|
||||
# buffer when it is saved.
|
||||
readback_debug = False
|
||||
|
||||
# Readback styles.
|
||||
style.create('readback_dialogue', 'say_dialogue', '')
|
||||
style.create('readback_thought', 'say_thought', '')
|
||||
|
||||
style.readback_dialogue.color = (255, 128, 128, 255)
|
||||
style.readback_thought.color = (255, 128, 128, 255)
|
||||
|
||||
|
||||
# No user-servicable parts below this line. ######################
|
||||
|
||||
# The readback buffer is a doubly-linked list of readback
|
||||
# objects.
|
||||
class Readback(object):
|
||||
def __init__(self, obj, args):
|
||||
self.older = None
|
||||
self.newer = None
|
||||
self.obj = obj
|
||||
self.args = args
|
||||
|
||||
def show(self):
|
||||
self.obj.readback(*self.args)
|
||||
|
||||
|
||||
readback_oldest = None
|
||||
readback_newest = None
|
||||
readback_count = 0
|
||||
|
||||
|
||||
# This saves a readback entry to the readback buffer.
|
||||
def readback_save(obj, *args):
|
||||
|
||||
store.readback_count += 1
|
||||
|
||||
if readback_limit and readback_count > readback_limit:
|
||||
store.readback_count -= 1
|
||||
readback_oldest.newer.older = None
|
||||
store.readback_oldest = readback_oldest.newer
|
||||
|
||||
rb = Readback(obj, args)
|
||||
|
||||
|
||||
if readback_newest:
|
||||
readback_newest.newer = rb
|
||||
else:
|
||||
store.readback_oldest = rb
|
||||
|
||||
rb.older = readback_newest
|
||||
|
||||
store.readback_newest = rb
|
||||
|
||||
|
||||
if readback_debug:
|
||||
|
||||
print "---- Readback Buffer ----"
|
||||
|
||||
rb = readback_oldest
|
||||
while rb:
|
||||
print rb.obj, rb.args
|
||||
rb = rb.newer
|
||||
|
||||
|
||||
# The rest of this file is replacing the default objects and
|
||||
# functions with versions that save things in the readback
|
||||
# buffer.
|
||||
|
||||
# Save the old character object.
|
||||
readback_OldCharacter = Character
|
||||
readback_OldDynamicCharacter = DynamicCharacter
|
||||
readback_oldmenu = menu
|
||||
|
||||
class Character(readback_OldCharacter):
|
||||
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
readback_OldCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, what)
|
||||
|
||||
def readback(self, what):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
|
||||
|
||||
class DynamicCharacter(readback_OldDynamicCharacter):
|
||||
def __init__(self, who,
|
||||
readback_style='readback_dialogue',
|
||||
**kwargs):
|
||||
|
||||
readback_OldDynamicCharacter.__init__(self, who, **kwargs)
|
||||
self.readback_style = readback_style
|
||||
|
||||
|
||||
def __call__(self, what, **kwargs):
|
||||
name = renpy.renpy.python.py_eval(self.name_expr)
|
||||
readback_OldDynamicCharacter.__call__(self, what, **kwargs)
|
||||
readback_save(self, name, what)
|
||||
|
||||
def readback(self, name, what):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.readback_style,
|
||||
window_style=self.window_style,
|
||||
interact=False,
|
||||
**self.properties)
|
||||
|
||||
|
||||
class Sayer(object):
|
||||
def __call__(self, who, what):
|
||||
renpy.display_say(who, what)
|
||||
readback_save(self, who, what)
|
||||
|
||||
def readback(self, who, what):
|
||||
renpy.display_say(who, what,
|
||||
what_style='readback_dialogue',
|
||||
interact=False)
|
||||
|
||||
|
||||
narrator = Character(None, what_style='say_thought')
|
||||
say = Sayer()
|
||||
|
||||
def readback(what):
|
||||
readback_save(narrator, what)
|
||||
|
||||
def menu(menuitems):
|
||||
rv = readback_oldmenu(menuitems)
|
||||
|
||||
text = '\n'.join([ l for l, v in menuitems
|
||||
if v is None or v == rv ])
|
||||
|
||||
readback(text)
|
||||
|
||||
return rv
|
||||
|
||||
# This stuff is involved in entering the readback mode.
|
||||
|
||||
def readback_mode():
|
||||
# Try rollback, first.
|
||||
renpy.rollback()
|
||||
|
||||
# If we made it here, we're into readback mode. So let's
|
||||
# go there now.
|
||||
|
||||
renpy.call_in_new_context("readback")
|
||||
|
||||
# Add in the readback function.
|
||||
config.underlay.append(renpy.Keymap(rollback=readback_mode))
|
||||
|
||||
|
||||
# This label is called in a new context, when the user succesfully
|
||||
# enters Readback mode.
|
||||
label readback:
|
||||
|
||||
# If we have an empty readback buffer, go home.
|
||||
if not readback_newest:
|
||||
return
|
||||
|
||||
python hide:
|
||||
|
||||
rb = readback_newest
|
||||
|
||||
while True:
|
||||
|
||||
rb.show()
|
||||
|
||||
ui.add(renpy.Keymap(rollback=lambda : "older",
|
||||
rollforward=lambda : "newer",
|
||||
dismiss=lambda : "dismiss",
|
||||
))
|
||||
|
||||
res = ui.interact()
|
||||
|
||||
if res == "newer":
|
||||
rb = rb.newer
|
||||
if not rb:
|
||||
break
|
||||
|
||||
elif res == "older":
|
||||
if rb.older:
|
||||
rb = rb.older
|
||||
|
||||
elif res == "dismiss":
|
||||
break
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
;;; Comments beginning with ";;;" are commentary added by PyTom to
|
||||
;;; try to help you understand this file.
|
||||
;;;
|
||||
;;; This installer expects that you will have supplied LICENSE.txt
|
||||
;;; and README.txt files in the root directory of your game.
|
||||
;;;
|
||||
;;; To use this, place it in the root directory of your game,
|
||||
;;; right-click on it, and choose "Compile NSIS script..."
|
||||
|
||||
;;; First up, change the settings below to match your game.
|
||||
|
||||
;;; The exe used to run your game.
|
||||
!define EXE "moonlight.exe"
|
||||
|
||||
;;; The exe containing the installer. This will be created in the directory
|
||||
;;; above the root directory containing your game.
|
||||
!define INSTALLER_EXE "moonlight-1.0ni.exe"
|
||||
|
||||
;;; The name and version.
|
||||
!define PRODUCT_NAME "Moonlight Walks"
|
||||
!define PRODUCT_VERSION "1.0"
|
||||
|
||||
;;; The following settings are only shown to the user in Add/Remove programs
|
||||
;;; but you'll still want to use them.
|
||||
|
||||
!define PRODUCT_WEB_SITE "http://www.bishoujo.us/moonlight/"
|
||||
!define PRODUCT_PUBLISHER "American Bishoujo"
|
||||
|
||||
;;; Ignore this next block of stuff. It's mostly boilerplate.
|
||||
!include "MUI.nsh"
|
||||
!define MUI_ABORTWARNING
|
||||
|
||||
;;; Change this to change the compression scheme.
|
||||
SetCompressor lzma
|
||||
|
||||
;;; You can change these to customize the bitmaps and icons used for
|
||||
;;; your installer and uninstaller. Bitmaps should be 150x57.
|
||||
|
||||
; !define MUI_HEADERIMAGE
|
||||
; !define MUI_HEADERIMAGE_BITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
|
||||
; !define MUI_HEADERIMAGE_UNBITMAP "${NSISDIR}\Contrib\Graphics\Header\nsis.bmp"
|
||||
!define MUI_ICON "${NSISDIR}\Contrib\Graphics\Icons\modern-install.ico"
|
||||
!define MUI_UNICON "${NSISDIR}\Contrib\Graphics\Icons\modern-uninstall.ico"
|
||||
|
||||
|
||||
;;; This is the sequencing of the pages that does the actual installation.
|
||||
;;; you can comment pages out, if you want to.
|
||||
|
||||
; Welcome page
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
; License page
|
||||
!insertmacro MUI_PAGE_LICENSE "LICENSE.txt"
|
||||
|
||||
; Directory page
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
; Instfiles page
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
; Finish page
|
||||
!define MUI_FINISHPAGE_SHOWREADME "$INSTDIR\README.txt"
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
; Uninstaller pages
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
|
||||
;;; Okay, that's it for the commentary. You're on your own from here...
|
||||
;;; but you probably won't need to touch anything below this point.
|
||||
|
||||
; Various other defines.
|
||||
!define PRODUCT_UNINST_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}"
|
||||
!define PRODUCT_UNINST_ROOT_KEY "HKLM"
|
||||
|
||||
; Language files
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
; Reserve files
|
||||
!insertmacro MUI_RESERVEFILE_INSTALLOPTIONS
|
||||
|
||||
|
||||
Name "${PRODUCT_NAME} ${PRODUCT_VERSION}"
|
||||
OutFile "..\${INSTALLER_EXE}"
|
||||
InstallDir "$PROGRAMFILES\${PRODUCT_NAME}"
|
||||
|
||||
Section "!${PRODUCT_NAME}" SEC01
|
||||
SetOutPath "$INSTDIR"
|
||||
SetOverwrite ifnewer
|
||||
|
||||
File /r /x renpy /x *.py /x *.pyw /x installer.nsi /x persistent *.*
|
||||
CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
|
||||
CreateShortCut "$DESKTOP\${PRODUCT_NAME}.lnk" "$INSTDIR\${EXE}"
|
||||
SectionEnd
|
||||
|
||||
Section -AdditionalIcons
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk" "$INSTDIR\uninst.exe"
|
||||
CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk" "$INSTDIR\README.txt"
|
||||
SectionEnd
|
||||
|
||||
Section -Post
|
||||
WriteUninstaller "$INSTDIR\uninst.exe"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayName" "$(^Name)"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "UninstallString" "$INSTDIR\uninst.exe"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayIcon" "$INSTDIR\${EXE}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "DisplayVersion" "${PRODUCT_VERSION}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "URLInfoAbout" "${PRODUCT_WEB_SITE}"
|
||||
WriteRegStr ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}" "Publisher" "${PRODUCT_PUBLISHER}"
|
||||
SectionEnd
|
||||
|
||||
|
||||
Function un.onUninstSuccess
|
||||
HideWindow
|
||||
MessageBox MB_ICONINFORMATION|MB_OK "$(^Name) was successfully removed from your computer."
|
||||
FunctionEnd
|
||||
|
||||
Function un.onInit
|
||||
MessageBox MB_ICONQUESTION|MB_YESNO|MB_DEFBUTTON2 "Are you sure you want to completely remove $(^Name) and all of its components?" IDYES +2
|
||||
Abort
|
||||
FunctionEnd
|
||||
|
||||
Section Uninstall
|
||||
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\README.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\Uninstall.lnk"
|
||||
Delete "$DESKTOP\${PRODUCT_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${PRODUCT_NAME}\${PRODUCT_NAME}.lnk"
|
||||
|
||||
RMDir "$SMPROGRAMS\${PRODUCT_NAME}"
|
||||
RMDir /r "$INSTDIR"
|
||||
|
||||
DeleteRegKey ${PRODUCT_UNINST_ROOT_KEY} "${PRODUCT_UNINST_KEY}"
|
||||
SetAutoClose true
|
||||
SectionEnd
|
||||
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
Well, [url="http://www.noradsanta.org"]NORAD has locked its
|
||||
interceptors onto Santa[/url], so I guess that means it's
|
||||
Christmas. And what a better way to celebrate than a new release of
|
||||
Ren'Py. (Well, how about with your families or something like that?)
|
||||
|
||||
Ren'Py 4.4 "Christmas Bonus" contains a number of feature
|
||||
enhancements, speed improvements, and bug fixes. Along with improved
|
||||
support for visual novels, this release lays the foundations for
|
||||
Stats-Based Dating Simulations. And it's my gift to you, downloadable
|
||||
from the Ren'Py website,
|
||||
|
||||
[url]http://www.bishoujo.us/renpy/[/url]
|
||||
|
||||
Upgrading a script from recent (4.2 or better) versions of Ren'Py
|
||||
should be as simple as copying over your old game directory, and
|
||||
perhaps deleting a line setting library.file_page_length.
|
||||
|
||||
This is a fairly major release, containing a bunch of new features and
|
||||
bug fixes. This is what you should be using if you want to make a
|
||||
Ren'Py game, at least until a new version comes out.
|
||||
|
||||
I'd like to thank Mikey for testing a pre-release of this to ensure
|
||||
that all the bugs with UTF-8 were well and truly squashed. Thanks to
|
||||
him, I now know what a LOWERCASE L WITH CARON is.
|
||||
|
||||
Anyway, as usual, feel free to post, email, or IM me if you have any
|
||||
questions, problems, comments, or suggestions. And have a great
|
||||
holiday, everyone.
|
||||
|
||||
[b]New in 4.4[/b]
|
||||
|
||||
Added a number of features to allow Ren'Py to support statistics-based
|
||||
dating simulations. The first is a new ui.bar() widget, which allows
|
||||
the display of a bar graph. The demo has been enhanced to include a
|
||||
stats and schedule screen, to show how this could be used. You can get
|
||||
to it by asking how to write your own games in the demo, or you can
|
||||
follow the below link to get a screenshot.
|
||||
|
||||
[url]http://www.bishoujo.us/hosted/screenshot.jpg[/url]
|
||||
|
||||
Another feature that has been added were 'jump expression' and 'call
|
||||
expression' statements, allowing computed jumps and calls. Together
|
||||
with an imporoved scheduler and yet-to-be written event dispatcher,
|
||||
this provides the foundation for SBDSes.
|
||||
|
||||
Now, setting a property on a style also sets the hover_ and idle_
|
||||
variants of that property. This makes changing a property of an
|
||||
inherited style a bit saner. In addition, hover now propagates to all
|
||||
children of a button. This means that hover will now work with the
|
||||
styles in the file chooser.
|
||||
|
||||
Added support for layering images. This support (invoked by passing a
|
||||
tuple as an image filename) allows a single image to be constructed
|
||||
from multiple image files, saving disk space while still keeping
|
||||
performance.
|
||||
|
||||
Improved skipping. Now, along with control causing skipping of seen
|
||||
dialogue, TAB toggles skip mode. An indicator displays to let you know
|
||||
that skip mode is enabled. Finally, if a game author wants to disable
|
||||
skipping, he can set config.allow_skipping to false.
|
||||
|
||||
(The following were also in 4.3.2, which was a private release to
|
||||
test the fixes for UTF-8 support.)
|
||||
|
||||
There were two bugs with UTF-8 support. The first was that we didn't
|
||||
understand the byte-order mark, and choked on the syntax error. The
|
||||
second was that the error-reporting code wasn't passing unicode errors
|
||||
through properly, so one couldn't even see the real error. Both are
|
||||
now fixed.
|
||||
|
||||
Improved the reporting of errors that occur during script loading and
|
||||
interpreter initialization by no longer giving a line number when none
|
||||
is appropriate.
|
||||
|
||||
Ren'Py now uses the name of the executable to choose the directory to
|
||||
read the script from. It does this by looking at the name of the
|
||||
executable that was used to run Ren'Py. It strips off the extension,
|
||||
and anything preceding the first underscore in the name, if such a
|
||||
thing exists. It then looks to see if that directory exists, and if it
|
||||
does, uses it. For example, if the program is named "run_en.exe", the
|
||||
"en" directory is used, while if the program is named "homestay.exe",
|
||||
the directory "homestay" is used.
|
||||
|
||||
The config variable config.searchpath is a list of directory that are
|
||||
searched for image files and other media (but not scripts, since all
|
||||
scripts are loaded before the variable can be set). This allows
|
||||
multiple game directories to share images, music, and other data files.
|
||||
|
||||
Once again, I redid the file chooser and default styles. The new file
|
||||
chooser displays 10 entries in two columns, with each entry being
|
||||
shown with an image. The files are now orginized into numbered slots,
|
||||
and it's possible to save in a slot without saving in all previous
|
||||
slots. There were also some changes to the non-user-visible parts of
|
||||
loading and saving.
|
||||
|
||||
[b]Speed Enhancements:[/b]
|
||||
|
||||
We now precompile python blocks in Ren'Py scripts, and store the
|
||||
compiled code in the .rpyc files. This makes loading an unmodified
|
||||
script significantly faster. On my system, this more than halved the
|
||||
time it took for the demo script to load. On the dowside, when a
|
||||
script is changed, it now can take somewhat longer for it to begin
|
||||
running, as all the changed code needs to be recompiled. Overall,
|
||||
it's a win, provided you ship the .rpyc files to your users.
|
||||
|
||||
Did another round of profiling, and found that styles were
|
||||
significanly slowing the system down. So I rewrote them to be much
|
||||
faster. This change shouldn't be user-visible, except that your game
|
||||
will feel a bit peppier.
|
||||
|
||||
Added a 1-entry cache for solid fill surfaces. If your game uses them,
|
||||
this might make it go faster.
|
||||
+4
-6
@@ -2,9 +2,9 @@
|
||||
# order.
|
||||
|
||||
# Some version numbers and things.
|
||||
version = "Ren'Py 4.8"
|
||||
script_version = 8000
|
||||
savegame_suffix = "-8.save"
|
||||
version = "Ren'Py 4.6"
|
||||
script_version = 5
|
||||
savegame_suffix = "-6.save"
|
||||
|
||||
|
||||
# Can be first, because has no dependencies, and may be imported
|
||||
@@ -32,10 +32,8 @@ import renpy.display.text # core
|
||||
import renpy.display.layout # core
|
||||
import renpy.display.behavior # layout
|
||||
import renpy.display.transition # core
|
||||
import renpy.display.im
|
||||
import renpy.display.image # core, behavior, im
|
||||
import renpy.display.image # core, behavior
|
||||
import renpy.display.video
|
||||
import renpy.display.focus
|
||||
|
||||
import renpy.ui
|
||||
|
||||
|
||||
+2
-7
@@ -1,4 +1,3 @@
|
||||
|
||||
# This file contains the AST for the Ren'Py script language. Each class
|
||||
# here corresponds to a statement in the script language.
|
||||
|
||||
@@ -118,6 +117,7 @@ def say_menu_with(expression):
|
||||
if renpy.game.preferences.transitions:
|
||||
renpy.game.interface.set_transition(what)
|
||||
|
||||
|
||||
class Say(Node):
|
||||
|
||||
def __init__(self, loc, who, what, with):
|
||||
@@ -136,7 +136,7 @@ class Say(Node):
|
||||
who = None
|
||||
|
||||
say_menu_with(self.with)
|
||||
renpy.exports.say(who, self.what)
|
||||
renpy.exports.say(who, self.what % renpy.game.store)
|
||||
|
||||
return self.next
|
||||
|
||||
@@ -286,10 +286,6 @@ def imspec_common(imspec, hide=False):
|
||||
for i in at_list:
|
||||
img = renpy.python.py_eval(i)(img)
|
||||
|
||||
# Update the set of images that have ever been seen.
|
||||
if not hide:
|
||||
renpy.game.persistent._seen_images[tuple(name)] = True
|
||||
|
||||
return key, img
|
||||
|
||||
def predict_imspec(imspec, callback):
|
||||
@@ -302,7 +298,6 @@ def predict_imspec(imspec, callback):
|
||||
return
|
||||
|
||||
im = renpy.exports.images[imspec[0]]
|
||||
|
||||
im.predict(callback)
|
||||
|
||||
|
||||
|
||||
+26
-27
@@ -47,9 +47,9 @@ profile = False
|
||||
# The directory save files will be saved to.
|
||||
savedir = None
|
||||
|
||||
# The number of screens worth of images that are allowed to live in the image
|
||||
# cache at once.
|
||||
image_cache_size = 8
|
||||
# The number of images that are allowed to live in the image cache
|
||||
# at once.
|
||||
image_cache_size = 10
|
||||
|
||||
# The number of statements we will analyze when doing predictive
|
||||
# loading. Please note that this is a total number of statements in a
|
||||
@@ -83,6 +83,9 @@ force_archives = False
|
||||
# An image file containing the mouse cursor, if one is defined.
|
||||
mouse = None
|
||||
|
||||
# The distance the keyboard moves the mouse, per 50 ms tick, in pixels.
|
||||
keymouse_distance = 5
|
||||
|
||||
# The default sound playback sample rate.
|
||||
sound_sample_rate = 44100
|
||||
|
||||
@@ -110,14 +113,6 @@ overlay_layers = [ 'overlay' ]
|
||||
# false otherwise.
|
||||
overlay_during_wait = True
|
||||
|
||||
# When using the keyboard to navigate, how much we penalize
|
||||
# distance out of the preferred direction.
|
||||
focus_crossrange_penalty = 1024
|
||||
|
||||
# If True, then we force all loading to occur before transitions
|
||||
# start.
|
||||
load_before_transition = True
|
||||
|
||||
# The keymap that is used to change keypresses and mouse events.
|
||||
keymap = dict(
|
||||
|
||||
@@ -128,18 +123,24 @@ keymap = dict(
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2', 'h' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Focus.
|
||||
focus_left = [ 'K_LEFT' ],
|
||||
focus_right = [ 'K_RIGHT' ],
|
||||
focus_up = [ 'K_UP' ],
|
||||
focus_down = [ 'K_DOWN' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
keymouse_right = [ 'K_RIGHT' ],
|
||||
keymouse_up = [ 'K_UP' ],
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouseup_1' ],
|
||||
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
|
||||
menu_keyup = [ 'K_UP' ],
|
||||
menu_keydown = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
@@ -147,21 +148,19 @@ keymap = dict(
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouseup_1' ],
|
||||
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
)
|
||||
|
||||
# A function that is called before each interaction, to update the
|
||||
# music that is currently playing.
|
||||
music_interact = None
|
||||
|
||||
# A function that is called when a music track ends, perhaps to
|
||||
# play another track.
|
||||
music_end_event = None
|
||||
|
||||
# The number of frames that Ren'Py has shown.
|
||||
frames = 0
|
||||
|
||||
def backup():
|
||||
|
||||
|
||||
+219
-520
@@ -1,11 +1,6 @@
|
||||
# This module contains code that handles the playing of sound and
|
||||
# music files.
|
||||
|
||||
# NOTE TO SELF:
|
||||
#
|
||||
# Remember to code defensively against mikey's computer that
|
||||
# doesn't have the sound card in it.
|
||||
|
||||
import pygame
|
||||
import renpy
|
||||
import sys # to detect windows.
|
||||
@@ -20,157 +15,257 @@ import sys # to detect windows.
|
||||
# read the same value from midiOutGetVolume() as we did before we tried
|
||||
# doing that.
|
||||
|
||||
# True if the mixer works, False if it doesn't, None if we have no
|
||||
# idea yet.
|
||||
mixer_works = None
|
||||
|
||||
# Common stuff.
|
||||
mixer_enabled = True
|
||||
|
||||
playing_midi = True
|
||||
fading = False
|
||||
master_music_volume = 1.0
|
||||
|
||||
# Windows stuff.
|
||||
midi_msf = 0.0
|
||||
last_raw_volume = -1
|
||||
|
||||
def init():
|
||||
windows_magic = False
|
||||
|
||||
global mixer_works
|
||||
global read_raw_volume
|
||||
global compute_midi_msf
|
||||
global set_music_volume
|
||||
global playing_midi
|
||||
if hasattr(sys, 'winver'):
|
||||
|
||||
if mixer_works is not None:
|
||||
return
|
||||
midi_msf = 0.0
|
||||
|
||||
try:
|
||||
pygame.mixer.music.get_volume()
|
||||
mixer_works = True
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
last_raw_volume = -1
|
||||
|
||||
def read_raw_volume():
|
||||
res = c_uint()
|
||||
|
||||
for i in range(0, winmm.midiOutGetNumDevs()):
|
||||
rv = winmm.midiOutGetVolume(i, byref(res))
|
||||
|
||||
if not rv:
|
||||
return res.value
|
||||
else:
|
||||
mixer_works = False
|
||||
print "Couldn't read raw midi volume."
|
||||
return -1
|
||||
|
||||
windows_magic = False
|
||||
|
||||
|
||||
if hasattr(sys, 'winver') and mixer_works:
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
|
||||
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
def compute_midi_msf():
|
||||
"""
|
||||
Computes the Midi MSF. Returns True if successful, False if otherwise.
|
||||
"""
|
||||
|
||||
# Don't update the MSF when fading is going on, or when not
|
||||
# playing a midi. (Except before playing any music whatsoever.)
|
||||
if fading or not playing_midi:
|
||||
return False
|
||||
|
||||
def _read_raw_volume():
|
||||
res = c_uint()
|
||||
global last_raw_volume
|
||||
|
||||
raw_vol = read_raw_volume()
|
||||
|
||||
for i in range(0, winmm.midiOutGetNumDevs()):
|
||||
rv = winmm.midiOutGetVolume(i, byref(res))
|
||||
|
||||
if not rv:
|
||||
return res.value
|
||||
else:
|
||||
print "Couldn't read raw midi volume."
|
||||
return -1
|
||||
|
||||
read_raw_volume = _read_raw_volume
|
||||
|
||||
def _compute_midi_msf():
|
||||
"""
|
||||
Computes the Midi MSF. Returns True if successful, False if otherwise.
|
||||
"""
|
||||
|
||||
# Don't update the MSF when fading is going on, or when not
|
||||
# playing a midi. (Except before playing any music whatsoever.)
|
||||
if fading or not playing_midi:
|
||||
return False
|
||||
|
||||
global last_raw_volume
|
||||
|
||||
raw_vol = read_raw_volume()
|
||||
|
||||
if raw_vol < 0:
|
||||
return False
|
||||
|
||||
# The case in which the volume hasn't changed recently.
|
||||
if raw_vol == last_raw_volume:
|
||||
return True
|
||||
|
||||
last_raw_volume = raw_vol
|
||||
|
||||
# print "raw_vol", raw_vol
|
||||
|
||||
# The fraction that the midi mixer is at.
|
||||
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
|
||||
|
||||
global midi_msf
|
||||
midi_msf = mixfrac / master_music_volume
|
||||
|
||||
# print "Midi msf is now:", midi_msf
|
||||
if raw_vol < 0:
|
||||
return False
|
||||
|
||||
# The case in which the volume hasn't changed recently.
|
||||
if raw_vol == last_raw_volume:
|
||||
return True
|
||||
|
||||
compute_midi_msf = _compute_midi_msf
|
||||
last_raw_volume = raw_vol
|
||||
|
||||
# This should get called after the music starts playing.
|
||||
def _set_music_volume(vol):
|
||||
# print "raw_vol", raw_vol
|
||||
|
||||
# The fraction that the midi mixer is at.
|
||||
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
global midi_msf
|
||||
midi_msf = mixfrac / master_music_volume
|
||||
|
||||
if playing_midi:
|
||||
# print "Midi msf is now:", midi_msf
|
||||
|
||||
vol *= midi_msf
|
||||
if vol > 1.0:
|
||||
vol = 1.0
|
||||
return True
|
||||
|
||||
try:
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
global last_raw_volume
|
||||
last_raw_volume = read_raw_volume()
|
||||
|
||||
set_music_volume = _set_music_volume
|
||||
|
||||
# Figure out the default msf, and set it up.
|
||||
windows_magic = compute_midi_msf()
|
||||
playing_midi = False
|
||||
|
||||
except Exception, e:
|
||||
print "Exception when trying to init music:", str(e)
|
||||
print "Falling back to Unix mode."
|
||||
|
||||
if not windows_magic:
|
||||
|
||||
def _compute_midi_msf():
|
||||
return
|
||||
|
||||
compute_midi_msf = _compute_midi_msf
|
||||
|
||||
def _set_music_volume(vol):
|
||||
if not mixer_works:
|
||||
return
|
||||
# This should get called after the music starts playing.
|
||||
def set_music_volume(vol):
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
try:
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
if playing_midi:
|
||||
|
||||
vol *= midi_msf
|
||||
if vol > 1.0:
|
||||
vol = 1.0
|
||||
|
||||
set_music_volume = _set_music_volume
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
global last_raw_volume
|
||||
last_raw_volume = read_raw_volume()
|
||||
|
||||
# Figure out the default msf, and set it up.
|
||||
windows_magic = compute_midi_msf()
|
||||
playing_midi = False
|
||||
|
||||
if mixer_works:
|
||||
pygame.mixer.music.set_endevent(renpy.display.core.MUSICEND)
|
||||
except Exception, e:
|
||||
print "Exception when trying to init music:", str(e)
|
||||
print "Falling back to Unix mode."
|
||||
|
||||
if not windows_magic:
|
||||
|
||||
def compute_midi_msf():
|
||||
return
|
||||
|
||||
def set_music_volume(vol):
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
playing_midi = False
|
||||
|
||||
# This detects if the filename is a midi, and sets playing_midi
|
||||
# appropriately.
|
||||
def detect_midi(fn):
|
||||
|
||||
fn = fn.lower()
|
||||
|
||||
global playing_midi
|
||||
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
|
||||
|
||||
|
||||
# Information about the currently playing track.
|
||||
current_music = None
|
||||
|
||||
def music_delay(offset):
|
||||
"""
|
||||
Returns the time left until the current music has been playing for
|
||||
offset seconds. If music is not playing, return None. May return
|
||||
a negative time.
|
||||
"""
|
||||
|
||||
mo = pygame.mixer.music.get_pos()
|
||||
if mo < 0:
|
||||
return None
|
||||
|
||||
mo /= 1000.0
|
||||
|
||||
return offset - mo
|
||||
|
||||
|
||||
|
||||
def music_start(filename, loops=-1, startpos=0.0):
|
||||
"""
|
||||
This starts music playing. If a music track is already playing,
|
||||
stops that track in favor of this one.
|
||||
|
||||
@param filename: The file that the music will be played from. This
|
||||
is relative to the game directory, and must be a real file (so it
|
||||
cannot be stored in an archive.)
|
||||
|
||||
@param loops: The number of times the music will loop after it
|
||||
finishes playing. If negative, the music will loop indefinitely.
|
||||
Please note that even once the song has finished, rollback or load
|
||||
may cause it to start playing again. So it may not be safe to have
|
||||
this set to a non-negative value.
|
||||
|
||||
@param startpos: The number of seconds into the music to start playing.
|
||||
"""
|
||||
|
||||
music_stop()
|
||||
renpy.game.context().scene_lists.music = (filename, loops, startpos)
|
||||
restore_music()
|
||||
|
||||
|
||||
def music_stop():
|
||||
"""
|
||||
Stops the currently playing music track.
|
||||
"""
|
||||
|
||||
renpy.game.context().scene_lists.music = None
|
||||
restore_music()
|
||||
|
||||
def restore_music():
|
||||
"""
|
||||
This makes sure that the current music matches the music found in
|
||||
the context.
|
||||
"""
|
||||
|
||||
global current_music
|
||||
global fading
|
||||
|
||||
compute_midi_msf()
|
||||
set_music_volume(1.0)
|
||||
|
||||
new_music = renpy.game.context().scene_lists.music
|
||||
|
||||
if not renpy.game.preferences.music:
|
||||
new_music = None
|
||||
|
||||
if current_music == new_music:
|
||||
return
|
||||
|
||||
if not mixer_enabled:
|
||||
return
|
||||
|
||||
# Usually, ignore errors.
|
||||
try:
|
||||
if current_music != new_music and current_music:
|
||||
current_music = None
|
||||
pygame.mixer.music.fadeout(int(renpy.config.fade_music * 1000))
|
||||
fading = True
|
||||
else:
|
||||
if not pygame.mixer.music.get_busy():
|
||||
fn, loops, startpos = new_music
|
||||
|
||||
fading = False
|
||||
detect_midi(fn)
|
||||
|
||||
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
|
||||
pygame.mixer.music.play(loops, startpos)
|
||||
|
||||
set_music_volume(master_music_volume)
|
||||
|
||||
current_music = new_music
|
||||
|
||||
except pygame.error, e:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
print "Error while trying to play music:", str(e)
|
||||
# Plays sounds.
|
||||
|
||||
def play(fn, loops=0):
|
||||
"""
|
||||
This plays the given sound. The sound must be in a wav file,
|
||||
and expected to have a sample rate 44100hz (changable with
|
||||
config.sound_sample_rate), 16 bit, stereo. These expectations may
|
||||
be violated, but that may lead to conversion delays.
|
||||
|
||||
Once a sound has been started, there's no way to stop it.
|
||||
|
||||
@param fn: The name of the file that the sound is read from. This
|
||||
file may be contained in a game directory or an archive.
|
||||
|
||||
@param loops: The number of extra times the sound will be
|
||||
played. (The default, 0, will play the sound once.)
|
||||
"""
|
||||
|
||||
if not fn:
|
||||
return
|
||||
|
||||
if not renpy.game.preferences.sound:
|
||||
return
|
||||
|
||||
if not mixer_enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
sound = pygame.mixer.Sound(renpy.loader.load(fn))
|
||||
sound.play()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def pre_init():
|
||||
try:
|
||||
bufsize = 4096
|
||||
@@ -194,14 +289,10 @@ def disable_mixer():
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if mixer_enabled:
|
||||
try:
|
||||
music_stop()
|
||||
pygame.mixer.quit()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
@@ -215,9 +306,6 @@ def enable_mixer():
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if not mixer_enabled:
|
||||
@@ -229,392 +317,3 @@ def enable_mixer():
|
||||
|
||||
mixer_enabled = True
|
||||
|
||||
|
||||
# This detects if the filename is a midi, and sets playing_midi
|
||||
# appropriately.
|
||||
def detect_midi(fn):
|
||||
|
||||
fn = fn.lower()
|
||||
|
||||
global playing_midi
|
||||
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
|
||||
|
||||
|
||||
def music_delay(offset):
|
||||
"""
|
||||
Returns the time left until the current music has been playing for
|
||||
offset seconds. If music is not playing, return None. May return
|
||||
a negative time.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return None
|
||||
|
||||
mo = pygame.mixer.music.get_pos()
|
||||
if mo < 0:
|
||||
return None
|
||||
|
||||
mo /= 1000.0
|
||||
|
||||
return offset - mo
|
||||
|
||||
|
||||
|
||||
# def music_start(filename, loops=-1, startpos=0.0):
|
||||
# """
|
||||
# This starts music playing. If a music track is already playing,
|
||||
# stops that track in favor of this one.
|
||||
|
||||
# @param filename: The file that the music will be played from. This
|
||||
# is relative to the game directory, and must be a real file (so it
|
||||
# cannot be stored in an archive.)
|
||||
|
||||
# @param loops: The number of times the music will loop after it
|
||||
# finishes playing. If negative, the music will loop indefinitely.
|
||||
# Please note that even once the song has finished, rollback or load
|
||||
# may cause it to start playing again. So it may not be safe to have
|
||||
# this set to a non-negative value.
|
||||
|
||||
# @param startpos: The number of seconds into the music to start playing.
|
||||
# """
|
||||
|
||||
# if not mixer_works:
|
||||
# return
|
||||
|
||||
# music_stop()
|
||||
# renpy.game.context().scene_lists.music = (filename, loops, startpos)
|
||||
# restore_music()
|
||||
|
||||
|
||||
# def music_stop():
|
||||
# """
|
||||
# Stops the currently playing music track.
|
||||
# """
|
||||
|
||||
# if not mixer_works:
|
||||
# return
|
||||
|
||||
# renpy.game.context().scene_lists.music = None
|
||||
# restore_music()
|
||||
|
||||
|
||||
# The filename of the currently playing piece of music.
|
||||
playing_filename = None
|
||||
|
||||
# The filename of the currently queued piece of music.
|
||||
queued_filename = None
|
||||
|
||||
# True if the music is in the process of fading out, or
|
||||
# False otherwise.
|
||||
fading = False
|
||||
|
||||
def music_update_volume():
|
||||
"""
|
||||
Sets the volume as appropriate for a midi.
|
||||
"""
|
||||
|
||||
if not playing_filename:
|
||||
return
|
||||
|
||||
detect_midi(playing_filename)
|
||||
set_music_volume(master_music_volume)
|
||||
|
||||
def music_interact():
|
||||
"""
|
||||
This is called before each interaction, to update the playing music
|
||||
(if necessary).
|
||||
"""
|
||||
|
||||
# Call the appropriate config function.
|
||||
if renpy.config.music_interact:
|
||||
renpy.config.music_interact()
|
||||
|
||||
def music_end_event():
|
||||
"""
|
||||
This is called by renpy.display.core when a track of music has
|
||||
endend.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
# shift the filenames.
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = queued_filename
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
music_update_volume()
|
||||
|
||||
# Call the appropriate function.
|
||||
if renpy.config.music_end_event:
|
||||
renpy.config.music_end_event()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_enabled():
|
||||
"""
|
||||
This should be called to check to see if music is enabled. If this
|
||||
returns False, then no music call should be made. Please note that
|
||||
this does not check preferences.music_enabled, so user code should
|
||||
also check that to see if music should be played.
|
||||
|
||||
This will return True if the mixer works and it has not been
|
||||
pre-empted by the video player, and False otherwise.
|
||||
|
||||
If this does not return True, none of the other music functions should
|
||||
be called.
|
||||
"""
|
||||
|
||||
return mixer_works and mixer_enabled
|
||||
|
||||
def music_play(filename):
|
||||
"""
|
||||
This causes the named music filename to be loaded in and played.
|
||||
Music loaded in this way immediately replaces the currently
|
||||
playing music.
|
||||
|
||||
The track is played once, and then stops. It's up to the higher-level
|
||||
music layer to ensure that a track that needs to be looped actually
|
||||
is.
|
||||
|
||||
The filename must refer to a real file, and not a file hidden in an
|
||||
archive.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = filename
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
pygame.mixer.music.load(renpy.loader.transfn(filename))
|
||||
|
||||
if not pygame.mixer.music.get_busy():
|
||||
pygame.mixer.music.play()
|
||||
|
||||
music_update_volume()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_queue(filename):
|
||||
"""
|
||||
This causes the given filename to be placed into the queue, to be
|
||||
played immediately after the currently playing track finishes.
|
||||
|
||||
The filename must refer to a real file, and not a file hidden in an
|
||||
archive.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
global queued_filename
|
||||
queued_filename = filename
|
||||
|
||||
pygame.mixer.music.queue(renpy.loader.transfn(filename))
|
||||
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_stop():
|
||||
"""
|
||||
This causes the music to be stopped immediately.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
global playing_filename
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
playing_filename = None
|
||||
queued_filename = None
|
||||
fading = False
|
||||
|
||||
pygame.mixer.music.stop()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_fadeout(seconds):
|
||||
"""
|
||||
This causes the music to be faded out over a period of
|
||||
time.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
global queued_filename
|
||||
global fading
|
||||
|
||||
queued_filename = None
|
||||
fading = True
|
||||
|
||||
pygame.mixer.music.fadeout(int(1000 * seconds))
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_filenames():
|
||||
"""
|
||||
Returns a tuple giving the currently playing music filename and
|
||||
the filename of the track in the queue. It returns None if there
|
||||
is no filename in either slot.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return None, None
|
||||
|
||||
try:
|
||||
|
||||
return playing_filename, queued_filename
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
return None, None
|
||||
|
||||
def music_fading():
|
||||
"""
|
||||
Returns True if the music is in the process of fading out, or False
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
return fading
|
||||
|
||||
def music_pause():
|
||||
"""
|
||||
Causes the currently playing music to be paused.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
pygame.mixer.music.pause()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def music_unpause():
|
||||
"""
|
||||
Causes the currently playing music to be unpaused, if it is
|
||||
currently paused.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
|
||||
pygame.mixer.music.unpause()
|
||||
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def sound_enabled():
|
||||
"""
|
||||
Returns True if it's possible to play sound, or False if sound
|
||||
should not be played.
|
||||
|
||||
Please note that this does not check preferences.sound. It's up
|
||||
to higher-level code (like renpy.play) to do that.
|
||||
"""
|
||||
|
||||
return mixer_works and mixer_enabled
|
||||
|
||||
def sound_play(filename, loops=0, channel=0):
|
||||
"""
|
||||
This causes the sound contained in the given filename to be played.
|
||||
|
||||
@param loops: The number of extra times that the sound will be
|
||||
played. If -1, the sound is played forever (until stopped).
|
||||
|
||||
@param channel: The channel that the sound will be played on, an
|
||||
integer from 0 to 7. This allows us to support playing up to 8
|
||||
sounds at once.
|
||||
"""
|
||||
|
||||
if not sound_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
chan = pygame.mixer.Channel(channel)
|
||||
chan.play(pygame.mixer.Sound(renpy.loader.load(filename)), loops)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
|
||||
def sound_stop(channel=0):
|
||||
"""
|
||||
This causes the sound currently playing in the specified channel
|
||||
to be stopped.
|
||||
"""
|
||||
|
||||
if not music_enabled():
|
||||
return
|
||||
|
||||
try:
|
||||
chan = pygame.mixer.Channel(channel)
|
||||
chan.stop()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def play(fn, loops=0):
|
||||
"""
|
||||
This plays the given sound. The sound must be in a wav file,
|
||||
and expected to have a sample rate 44100hz (changeable with
|
||||
config.sound_sample_rate), 16 bit, stereo. These expectations may
|
||||
be violated, but that may lead to conversion delays.
|
||||
|
||||
Once a sound has been started, there's no way to stop it.
|
||||
|
||||
@param fn: The name of the file that the sound is read from. This
|
||||
file may be contained in a game directory or an archive.
|
||||
|
||||
@param loops: The number of extra times the sound will be
|
||||
played. (The default, 0, will play the sound once.)
|
||||
|
||||
This plays the sound on channel 0.
|
||||
"""
|
||||
|
||||
if not fn:
|
||||
return
|
||||
|
||||
if not renpy.game.preferences.sound:
|
||||
return
|
||||
|
||||
sound_play(fn, loops=loops)
|
||||
|
||||
+297
-74
@@ -101,16 +101,41 @@ class Keymap(renpy.display.layout.Null):
|
||||
|
||||
for name, action in self.keymap.iteritems():
|
||||
if map_event(ev, name):
|
||||
rv = action()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
action()
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
# def render(self, width, height, st):
|
||||
# return None
|
||||
|
||||
class KeymouseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class that causes the keyboard to move the mouse. It's
|
||||
useful on the game and key menus, as well as in imagemaps and the
|
||||
like.
|
||||
"""
|
||||
|
||||
def event(self, ev, x, y):
|
||||
if ev.type == renpy.display.core.DISPLAYTIME:
|
||||
|
||||
pressed = pygame.key.get_pressed()
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
ox, oy = x, y
|
||||
|
||||
if is_pressed(pressed, "keymouse_left"):
|
||||
x -= renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_right"):
|
||||
x += renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_up"):
|
||||
y -= renpy.config.keymouse_distance
|
||||
if is_pressed(pressed, "keymouse_down"):
|
||||
y += renpy.config.keymouse_distance
|
||||
|
||||
if (x, y) != (ox, oy):
|
||||
pygame.mouse.set_pos((x, y))
|
||||
|
||||
return None
|
||||
|
||||
class PauseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class implementing the Pause behavior, which is to
|
||||
@@ -139,11 +164,10 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
mouse button.
|
||||
"""
|
||||
|
||||
focusable = True
|
||||
|
||||
def __init__(self, default=True, **properties):
|
||||
super(SayBehavior, self).__init__(default=default, **properties)
|
||||
def __init__(self):
|
||||
super(SayBehavior, self).__init__()
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
@@ -155,7 +179,8 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
elif renpy.game.context().seen_current(True):
|
||||
return True
|
||||
|
||||
if map_event(ev, "dismiss") and self.is_focused():
|
||||
|
||||
if map_event(ev, "dismiss"):
|
||||
return True
|
||||
|
||||
if map_event(ev, "rollforward"):
|
||||
@@ -163,70 +188,211 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
return True
|
||||
|
||||
return None
|
||||
|
||||
class Menu(renpy.display.layout.VBox):
|
||||
|
||||
def __init__(self, menuitems, style='menu', **properties):
|
||||
"""
|
||||
@param menuitems: A list of menuitem tuples. The first element
|
||||
of each tuple is the string that should be displayed to the
|
||||
user. The second item is the value that should be returned if
|
||||
this item is selected, or None to indicate that this item is a
|
||||
caption.
|
||||
"""
|
||||
|
||||
super(Menu, self).__init__(style=style, **properties)
|
||||
|
||||
self.selected = None
|
||||
self.results = [ ]
|
||||
|
||||
# self.caption_style = renpy.style.Style('menu_caption', { })
|
||||
# self.selected_style = renpy.style.Style('menu_choice', { })
|
||||
# self.unselected_style = renpy.style.Style('menu_choice', { })
|
||||
|
||||
# self.selected_style.set_prefix('hover_')
|
||||
# self.unselected_style.set_prefix('idle_')
|
||||
|
||||
for i, (caption, result) in enumerate(menuitems):
|
||||
|
||||
if result is not None:
|
||||
style = 'menu_choice'
|
||||
else:
|
||||
style = 'menu_caption'
|
||||
|
||||
self.add(renpy.display.text.Text(caption, style=style))
|
||||
|
||||
if self.selected is None and result is not None:
|
||||
self.selected = i
|
||||
|
||||
self.results.append(result)
|
||||
|
||||
self.update_styles()
|
||||
|
||||
def update_styles(self):
|
||||
"""
|
||||
This updates the colors of our children to reflect the
|
||||
one that has been selected by the user.
|
||||
"""
|
||||
|
||||
for i, (child, result) in enumerate(zip(self.children, self.results)):
|
||||
|
||||
# Captions should stay the default text color.
|
||||
if result is None:
|
||||
continue
|
||||
|
||||
# Actual choices change color if they are selected or not.
|
||||
if i == self.selected:
|
||||
child.set_style_prefix('hover_')
|
||||
else:
|
||||
child.set_style_prefix('idle_')
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
Processes events.
|
||||
"""
|
||||
|
||||
# print ev
|
||||
# print x, y
|
||||
|
||||
old_selected = self.selected
|
||||
mouse_select = False
|
||||
|
||||
# Change selection based on mouse position.
|
||||
if ev.type == MOUSEMOTION or map_event(ev, "menu_mouseselect"):
|
||||
target = self.child_at_point(x, y)
|
||||
if target is None:
|
||||
return None
|
||||
|
||||
if self.results[target] is not None:
|
||||
self.selected = target
|
||||
mouse_select = True
|
||||
|
||||
# Change selection based on keypress.
|
||||
if map_event(ev, "menu_keydown"):
|
||||
|
||||
selected = self.selected
|
||||
|
||||
while selected < len(self.results) - 1:
|
||||
selected += 1
|
||||
if self.results[selected] is not None:
|
||||
self.selected = selected
|
||||
break
|
||||
|
||||
# Change selection based on keypress.
|
||||
if map_event(ev, "menu_keyup"):
|
||||
|
||||
selected = self.selected
|
||||
|
||||
while selected > 0:
|
||||
selected -= 1
|
||||
if self.results[selected] is not None:
|
||||
self.selected = selected
|
||||
break
|
||||
|
||||
# If the selected item changed, update the display.
|
||||
if self.selected != old_selected:
|
||||
|
||||
self.children[self.selected].set_style_prefix("hover_")
|
||||
self.children[old_selected].set_style_prefix("idle_")
|
||||
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
# renpy.display.render.redraw(self, 0)
|
||||
|
||||
# Make selection based on keypress or mouse click.
|
||||
if map_event(ev, "menu_keyselect") or \
|
||||
(mouse_select and map_event(ev, "menu_mouseselect")):
|
||||
|
||||
self.children[self.selected].set_style_prefix("activate_")
|
||||
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
return self.results[self.selected]
|
||||
|
||||
return None
|
||||
|
||||
class Button(renpy.display.layout.Window):
|
||||
|
||||
|
||||
def __init__(self, child, style='button', clicked=None,
|
||||
hovered=None, **properties):
|
||||
|
||||
super(Button, self).__init__(child, style=style, **properties)
|
||||
self.style.set_prefix('idle_')
|
||||
|
||||
self.activated = False
|
||||
|
||||
self.old_hover = False
|
||||
self.clicked = clicked
|
||||
self.hovered = hovered
|
||||
self.focusable = clicked is not None
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = super(Button, self).render(width, height, st)
|
||||
if self.activated:
|
||||
self.set_style_prefix('activate_')
|
||||
elif self.old_hover:
|
||||
self.set_style_prefix('hover_')
|
||||
else:
|
||||
self.set_style_prefix('idle_')
|
||||
|
||||
if self.clicked:
|
||||
rv.add_focus(self,
|
||||
None,
|
||||
self.style.left_margin,
|
||||
self.style.top_margin,
|
||||
rv.width - self.style.right_margin,
|
||||
rv.height - self.style.bottom_margin)
|
||||
|
||||
return rv
|
||||
|
||||
return super(Button, self).render(width, height, st)
|
||||
|
||||
def set_hover(self, hover):
|
||||
"""
|
||||
Called when we change from hovered to un-hovered, or
|
||||
vice-versa.
|
||||
"""
|
||||
|
||||
# if hover:
|
||||
# self.style.set_prefix('hover_')
|
||||
# else:
|
||||
# self.style.set_prefix('idle_')
|
||||
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
# We deactivate on an event.
|
||||
if self.activated:
|
||||
self.activated = False
|
||||
self.activated = False
|
||||
|
||||
if self.focusable:
|
||||
if self.is_focused():
|
||||
self.set_style_prefix('hover_')
|
||||
inside = False
|
||||
|
||||
width, height = self.window_size
|
||||
|
||||
if x >= 0 and x < width and y >= 0 and y < height:
|
||||
inside = True
|
||||
|
||||
if self.style.enable_hover and ev.type == MOUSEMOTION:
|
||||
|
||||
if self.old_hover != inside:
|
||||
self.old_hover = inside
|
||||
self.set_hover(inside)
|
||||
|
||||
if inside:
|
||||
if self.hovered:
|
||||
self.hovered()
|
||||
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if map_event(ev, "button_select"):
|
||||
if inside and self.clicked:
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
|
||||
self.activated = True
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
self.set_style_prefix('idle_')
|
||||
else:
|
||||
self.set_style_prefix('insensitive_')
|
||||
|
||||
# If not focused, ignore all events.
|
||||
if not self.is_focused():
|
||||
return None
|
||||
|
||||
# If clicked,
|
||||
if map_event(ev, "button_select") and self.clicked:
|
||||
|
||||
self.activated = True
|
||||
|
||||
self.set_style_prefix('activate_')
|
||||
renpy.display.audio.play(self.style.sound)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
return super(Button, self).event(ev, x, y)
|
||||
|
||||
# Reimplementation of the TextButton widget as a Button and a Text
|
||||
# widget.
|
||||
def TextButton(text, style='button', text_style='button_text',
|
||||
@@ -235,6 +401,29 @@ def TextButton(text, style='button', text_style='button_text',
|
||||
text = renpy.display.text.Text(text, style=text_style)
|
||||
return Button(text, style=style, clicked=clicked, **properties)
|
||||
|
||||
|
||||
|
||||
# class TextButton(Button):
|
||||
|
||||
# def __init__(self, text, style='button', text_style='button_text',
|
||||
# clicked=None):
|
||||
|
||||
# self.text_widget = renpy.display.text.Text(text, style=text_style)
|
||||
|
||||
# super(TextButton, self).__init__(self.text_widget,
|
||||
# style=style,
|
||||
# clicked=clicked)
|
||||
|
||||
# self.text_widget.style.set_prefix('idle_')
|
||||
|
||||
# def set_hover(self, hover):
|
||||
# super(TextButton, self).set_hover(hover)
|
||||
|
||||
# if hover:
|
||||
# self.text_widget.style.set_prefix("hover_")
|
||||
# else:
|
||||
# self.text_widget.style.set_prefix("idle_")
|
||||
|
||||
|
||||
class Input(renpy.display.text.Text):
|
||||
"""
|
||||
@@ -242,47 +431,34 @@ class Input(renpy.display.text.Text):
|
||||
"""
|
||||
|
||||
def __init__(self, default, length=None,
|
||||
style='input_text',
|
||||
allow=None,
|
||||
exclude=None,
|
||||
**properties):
|
||||
|
||||
super(Input, self).__init__(default.replace("{", "{{") + "_", style=style, **properties)
|
||||
|
||||
style='input_text', **properties):
|
||||
super(Input, self).__init__(default + "_", style=style, **properties)
|
||||
self.content = unicode(default)
|
||||
self.length = length
|
||||
|
||||
self.allow = allow
|
||||
self.exclude = exclude
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if map_event(ev, "input_backspace"):
|
||||
if self.content:
|
||||
self.content = self.content[:-1]
|
||||
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
self.set_text(self.content + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
|
||||
elif map_event(ev, "input_enter"):
|
||||
return self.content
|
||||
|
||||
elif ev.type == KEYDOWN and ev.unicode:
|
||||
if ord(ev.unicode[0]) < 32:
|
||||
return None
|
||||
return None
|
||||
|
||||
if self.length and len(self.content) >= self.length:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
if self.allow and ev.unicode not in self.allow:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
if self.exclude and ev.unicode in self.exclude:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
return None
|
||||
|
||||
self.content += ev.unicode
|
||||
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
self.set_text(self.content + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
@@ -294,7 +470,7 @@ class Bar(renpy.display.core.Displayable):
|
||||
to clicks on that value.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height, range, value,
|
||||
def __init__(self, width, height, range, value, clicked=None,
|
||||
style='bar', **properties):
|
||||
|
||||
super(Bar, self).__init__()
|
||||
@@ -306,14 +482,61 @@ class Bar(renpy.display.core.Displayable):
|
||||
self.range = range
|
||||
self.value = value
|
||||
|
||||
self.clicked = clicked
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if not self.clicked:
|
||||
return
|
||||
|
||||
if not map_event(ev, 'bar_click'):
|
||||
return
|
||||
|
||||
if not (0 <= x < self.width and 0 <= y <= self.height):
|
||||
return
|
||||
|
||||
# print x, y
|
||||
|
||||
lgutter = self.style.left_gutter
|
||||
rgutter = self.style.right_gutter
|
||||
|
||||
if x < lgutter:
|
||||
value = 0
|
||||
elif x > self.width - rgutter:
|
||||
value = self.range
|
||||
else:
|
||||
barwidth = self.width - lgutter - rgutter
|
||||
|
||||
# This makes it easier to select 100%.
|
||||
x = x - lgutter
|
||||
x = x + (barwidth / self.range // 2)
|
||||
|
||||
value = x * self.range / barwidth
|
||||
|
||||
value = max(value, 0)
|
||||
|
||||
|
||||
rv = self.clicked(value)
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
width = self.width
|
||||
height = self.height
|
||||
|
||||
lgutter = 0
|
||||
rgutter = 0
|
||||
# The amount of space taken up by the bars.
|
||||
|
||||
if self.clicked:
|
||||
lgutter = self.style.left_gutter
|
||||
rgutter = self.style.right_gutter
|
||||
else:
|
||||
lgutter = 0
|
||||
rgutter = 0
|
||||
|
||||
barwidth = width - lgutter - rgutter
|
||||
|
||||
@@ -346,7 +569,7 @@ class Conditional(renpy.display.layout.Container):
|
||||
self.condition = condition
|
||||
self.null = renpy.display.layout.Null()
|
||||
|
||||
self.state = eval(self.condition, vars(renpy.store))
|
||||
self.state = eval(self.condition, renpy.game.store)
|
||||
|
||||
def render(self, width, height, st):
|
||||
if self.state:
|
||||
@@ -356,7 +579,7 @@ class Conditional(renpy.display.layout.Container):
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
state = eval(self.condition, vars(renpy.store))
|
||||
state = eval(self.condition, renpy.game.store)
|
||||
|
||||
if state != self.state:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
+47
-138
@@ -2,16 +2,15 @@
|
||||
# window.
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
import os
|
||||
import time
|
||||
import cStringIO
|
||||
|
||||
# KEYREPEATEVENT = USEREVENT + 1
|
||||
DISPLAYTIME = USEREVENT + 2
|
||||
MUSICEND = USEREVENT + 3
|
||||
|
||||
# The number of msec
|
||||
DISPLAYTIME_INTERVAL = 50
|
||||
@@ -34,40 +33,9 @@ class Displayable(renpy.object.Object):
|
||||
their fields.
|
||||
"""
|
||||
|
||||
focusable = False
|
||||
|
||||
def __init__(self, focus=None, default=False, style='default', **properties):
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.style_prefix = 'insensitive_'
|
||||
self.focus_name = focus
|
||||
self.default = default
|
||||
|
||||
def find_focusable(self, callback, focus_name):
|
||||
if self.focusable:
|
||||
callback(self, self.focus_name or focus_name)
|
||||
|
||||
|
||||
def focus(self, default=False):
|
||||
"""
|
||||
Called to indicate that this widget has the focus.
|
||||
"""
|
||||
|
||||
if self.style.enable_hover:
|
||||
self.set_style_prefix("hover_")
|
||||
|
||||
if not default:
|
||||
renpy.display.audio.play(self.style.sound)
|
||||
|
||||
def unfocus(self):
|
||||
"""
|
||||
Called to indicate that this widget has become unfocused.
|
||||
"""
|
||||
|
||||
if self.style.enable_hover:
|
||||
self.set_style_prefix("idle_")
|
||||
|
||||
def is_focused(self):
|
||||
return renpy.game.context().scene_lists.focused is self
|
||||
def __init__(self):
|
||||
self.style = None
|
||||
self.style_prefix = None
|
||||
|
||||
def set_style_prefix(self, prefix):
|
||||
"""
|
||||
@@ -78,7 +46,9 @@ class Displayable(renpy.object.Object):
|
||||
if prefix == self.style_prefix:
|
||||
return
|
||||
|
||||
self.style.set_prefix(prefix)
|
||||
if self.style:
|
||||
self.style.set_prefix(prefix)
|
||||
|
||||
self.style_prefix = prefix
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
@@ -136,8 +106,8 @@ class Displayable(renpy.object.Object):
|
||||
the images it may want to load.
|
||||
"""
|
||||
|
||||
if self.style and self.style.background:
|
||||
self.style.background.predict(callback)
|
||||
return
|
||||
|
||||
|
||||
def place(self, dest, x, y, width, height, surf):
|
||||
"""
|
||||
@@ -225,7 +195,6 @@ class SceneLists(object):
|
||||
|
||||
@ivar music: Opaque information about the music that is being played.
|
||||
|
||||
@ivar focused: The widget that is currently focused.
|
||||
"""
|
||||
|
||||
def __init__(self, oldsl=None):
|
||||
@@ -242,7 +211,6 @@ class SceneLists(object):
|
||||
self.music = oldsl.music
|
||||
self.sticky_positions = oldsl.sticky_positions.copy()
|
||||
self.movie = oldsl.movie
|
||||
self.focused = oldsl.focused
|
||||
|
||||
else:
|
||||
for i in renpy.config.layers:
|
||||
@@ -251,7 +219,6 @@ class SceneLists(object):
|
||||
self.music = None
|
||||
self.movie = None
|
||||
self.sticky_positions = { }
|
||||
self.focused = None
|
||||
|
||||
def rollback_copy(self):
|
||||
"""
|
||||
@@ -264,8 +231,6 @@ class SceneLists(object):
|
||||
for i in renpy.config.overlay_layers:
|
||||
rv.layers[i] = [ ]
|
||||
|
||||
rv.focused = None
|
||||
|
||||
# rv.master = self.master[:]
|
||||
# rv.transient = self.transient[:]
|
||||
|
||||
@@ -380,14 +345,11 @@ class Display(object):
|
||||
|
||||
renpy.display.audio.pre_init()
|
||||
pygame.init()
|
||||
renpy.display.audio.init()
|
||||
|
||||
self.fullscreen = renpy.game.preferences.fullscreen
|
||||
fsflag = 0
|
||||
|
||||
fullscreen = self.fullscreen and not os.environ.get('RENPY_DISABLE_FULLSCREEN', False)
|
||||
|
||||
if fullscreen:
|
||||
if self.fullscreen:
|
||||
fsflag = FULLSCREEN
|
||||
|
||||
# The window we display things in.
|
||||
@@ -409,12 +371,12 @@ class Display(object):
|
||||
pygame.display.set_caption(renpy.config.window_title)
|
||||
|
||||
if renpy.config.window_icon:
|
||||
pygame.display.set_icon(renpy.display.im.load_image(renpy.config.window_icon))
|
||||
pygame.display.set_icon(renpy.display.image.cache.load_image(renpy.config.window_icon))
|
||||
|
||||
|
||||
# Load the mouse image, if any.
|
||||
if renpy.config.mouse:
|
||||
self.mouse = renpy.display.im.load_image(renpy.config.mouse)
|
||||
self.mouse = renpy.display.image.cache.load_image(renpy.config.mouse)
|
||||
pygame.mouse.set_visible(False)
|
||||
else:
|
||||
self.mouse = None
|
||||
@@ -498,7 +460,6 @@ class Display(object):
|
||||
|
||||
self.suppress_mouse = suppress_blit
|
||||
|
||||
renpy.display.focus.take_focuses(surftree.focuses)
|
||||
|
||||
def save_screenshot(self, filename):
|
||||
"""
|
||||
@@ -552,9 +513,6 @@ class Interface(object):
|
||||
@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.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
@@ -567,7 +525,6 @@ class Interface(object):
|
||||
self.quick_quit = False
|
||||
self.force_redraw = False
|
||||
self.restart_interaction = False
|
||||
self.pushed_event = None
|
||||
|
||||
def take_screenshot(self, scale):
|
||||
"""
|
||||
@@ -621,41 +578,12 @@ class Interface(object):
|
||||
|
||||
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 == NOEVENT:
|
||||
return None
|
||||
|
||||
self.pushed_event = ev
|
||||
return ev
|
||||
|
||||
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
|
||||
return rv
|
||||
|
||||
# We load at most one image per wait.
|
||||
if renpy.display.im.cache.needs_preload():
|
||||
ev = pygame.event.poll()
|
||||
if ev.type != NOEVENT:
|
||||
return ev
|
||||
|
||||
renpy.display.im.cache.preload()
|
||||
|
||||
return pygame.event.wait()
|
||||
|
||||
|
||||
@@ -671,7 +599,7 @@ class Interface(object):
|
||||
root = renpy.display.layout.Fixed()
|
||||
|
||||
for layer in renpy.config.layers:
|
||||
f = renpy.display.layout.Fixed(focus=layer)
|
||||
f = renpy.display.layout.Fixed()
|
||||
f.append_scene_list(scene_lists.layers[layer])
|
||||
|
||||
rv[layer] = f
|
||||
@@ -688,23 +616,16 @@ class Interface(object):
|
||||
keyword arguments are passed off to interact_core.
|
||||
"""
|
||||
|
||||
# These things can be done once per interaction.
|
||||
repeat = True
|
||||
|
||||
try:
|
||||
|
||||
repeat = True
|
||||
|
||||
while repeat:
|
||||
repeat, rv = self.interact_core(**kwargs)
|
||||
while repeat:
|
||||
repeat, rv = self.interact_core(**kwargs)
|
||||
|
||||
return rv
|
||||
|
||||
finally:
|
||||
|
||||
# Clean out transient stuff at the end of an interaction.
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
scene_lists.replace_transient()
|
||||
# Clean out transient stuff at the end of an interaction.
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
scene_lists.replace_transient()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def interact_core(self,
|
||||
@@ -742,14 +663,13 @@ class Interface(object):
|
||||
|
||||
# frames = 0
|
||||
|
||||
# Update the music, if necessary.
|
||||
for i in pygame.event.get([ MUSICEND ]):
|
||||
renpy.display.audio.music_end_event()
|
||||
|
||||
renpy.display.audio.music_interact()
|
||||
## Expensive things we want to do before we pick the start_time.
|
||||
|
||||
# Tick time forward.
|
||||
renpy.display.im.cache.tick()
|
||||
renpy.display.image.cache.tick()
|
||||
|
||||
# Predict images.
|
||||
renpy.game.context().predict(renpy.display.image.cache.preload_image)
|
||||
|
||||
# Set up key repeats.
|
||||
# pygame.time.set_timer(KEYREPEATEVENT, renpy.config.skip_delay)
|
||||
@@ -760,9 +680,12 @@ class Interface(object):
|
||||
# Clear some events.
|
||||
pygame.event.clear((MOUSEMOTION, DISPLAYTIME,
|
||||
MOUSEBUTTONUP, MOUSEBUTTONDOWN))
|
||||
|
||||
|
||||
# Figure out the scene list we want to show.
|
||||
# Figure out the scene list we want to show.
|
||||
start_time = time.time()
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
|
||||
|
||||
# Figure out what the overlay layer should look like.
|
||||
for i in renpy.config.overlay_layers:
|
||||
@@ -788,15 +711,7 @@ class Interface(object):
|
||||
|
||||
# Figure out the scene. (All of the layers, and the root.)
|
||||
scene = self.compute_scene(scene_lists)
|
||||
|
||||
# If necessary, load all images here.
|
||||
if renpy.config.load_before_transition:
|
||||
for w in scene.itervalues():
|
||||
w.predict(renpy.display.im.cache.get)
|
||||
|
||||
# The start time of transitions.
|
||||
start_time = time.time()
|
||||
|
||||
|
||||
# The root widget of all of the layers.
|
||||
layers_root = renpy.display.layout.Fixed()
|
||||
|
||||
@@ -838,20 +753,13 @@ class Interface(object):
|
||||
# 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(root_widget)
|
||||
|
||||
# Redraw the screen.
|
||||
renpy.display.render.process_redraws()
|
||||
needs_redraw = True
|
||||
|
||||
# Post an event that moves us to the current mouse position.
|
||||
# pygame.event.post(pygame.event.Event(MOUSEMOTION,
|
||||
# pos=pygame.mouse.get_pos()))
|
||||
|
||||
# We only want to do prediction once, but we will defer it as
|
||||
# long as possible.
|
||||
did_prediction = False
|
||||
pygame.event.post(pygame.event.Event(MOUSEMOTION,
|
||||
pos=pygame.mouse.get_pos()))
|
||||
|
||||
rv = None
|
||||
|
||||
@@ -883,13 +791,13 @@ class Interface(object):
|
||||
|
||||
self.display.show(root_widget, suppress_blit)
|
||||
|
||||
renpy.config.frames += 1
|
||||
# frames = frames + 1
|
||||
|
||||
# If profiling is enabled, report the profile time.
|
||||
if renpy.config.profile:
|
||||
new_time = time.time()
|
||||
print "Profile: Redraw took %f seconds." % (new_time - draw_start)
|
||||
print "Profile: %f seconds to complete event." % (new_time - self.profile_time)
|
||||
print "Profile: %f seconds between event and display." % (new_time - self.profile_time)
|
||||
|
||||
# Draw the mouse, if it needs drawing.
|
||||
if show_mouse:
|
||||
@@ -900,23 +808,19 @@ class Interface(object):
|
||||
|
||||
# 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 needs_redraw and not pygame.event.peek():
|
||||
continue
|
||||
|
||||
# Predict images, if we haven't done so already.
|
||||
if not did_prediction and not self.event_peek():
|
||||
root_widget.predict(renpy.display.im.cache.preload_image)
|
||||
renpy.game.context().predict(renpy.display.im.cache.preload_image)
|
||||
did_prediction = True
|
||||
# While we have nothing to do, preload images.
|
||||
while renpy.display.image.cache.needs_preload() and \
|
||||
not pygame.event.peek():
|
||||
renpy.display.image.cache.preload()
|
||||
|
||||
try:
|
||||
# ev = pygame.event.wait()
|
||||
ev = self.event_wait()
|
||||
self.profile_time = time.time()
|
||||
|
||||
# A song just ended.
|
||||
if ev.type == MUSICEND:
|
||||
renpy.display.audio.music_end_event()
|
||||
|
||||
if ev.type == DISPLAYTIME:
|
||||
|
||||
events = 1 + len(pygame.event.get([DISPLAYTIME]))
|
||||
@@ -926,6 +830,12 @@ class Interface(object):
|
||||
ev = pygame.event.Event(DISPLAYTIME, {},
|
||||
duration=(time.time() - start_time))
|
||||
|
||||
# Update the playing music, if necessary.
|
||||
|
||||
# This needs to be here so that we eventually start a
|
||||
# new song at the end of a fadeout.
|
||||
renpy.display.audio.restore_music()
|
||||
|
||||
# Handle skipping.
|
||||
renpy.display.behavior.skipping(ev)
|
||||
|
||||
@@ -945,9 +855,8 @@ class Interface(object):
|
||||
if len(evs):
|
||||
ev = evs[-1]
|
||||
|
||||
renpy.display.focus.event_handler(ev)
|
||||
|
||||
# x, y = getattr(ev, 'pos', (0, 0))
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
|
||||
rv = root_widget.event(ev, x, y)
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
# This file contains code to manage focus on the display.
|
||||
|
||||
import renpy
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
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 __iter__(self):
|
||||
return iter((self.widget, self.arg, self.x, self.y, self.w, self.h))
|
||||
|
||||
|
||||
# Sets the currently focused widget.
|
||||
def set_focused(widget):
|
||||
renpy.game.context().scene_lists.focused = widget
|
||||
|
||||
# Gets the currently focused widget.
|
||||
def get_focused():
|
||||
return renpy.game.context().scene_lists.focused
|
||||
|
||||
|
||||
# The current list of focuses that we know about.
|
||||
focus_list = [ ]
|
||||
|
||||
# This takes in a focus list from the rendering system.
|
||||
def take_focuses(fl):
|
||||
global focus_list
|
||||
focus_list = fl
|
||||
|
||||
# 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.
|
||||
|
||||
def before_interact(root):
|
||||
|
||||
# a list of focusable, name tuples.
|
||||
fwn = [ ]
|
||||
|
||||
def callback(f, n):
|
||||
fwn.append((f, n))
|
||||
|
||||
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.full_focus_name:
|
||||
current = f
|
||||
set_focused(f)
|
||||
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)
|
||||
break
|
||||
else:
|
||||
set_focused(None)
|
||||
|
||||
|
||||
# Finally, mark the current widget as the focused widget, and
|
||||
# all other widgets as unfocused.
|
||||
for f, n in fwn:
|
||||
if f is current:
|
||||
f.focus(default=True)
|
||||
else:
|
||||
f.unfocus()
|
||||
|
||||
|
||||
|
||||
# This changes the focus to be the widget contained inside the new
|
||||
# focus object.
|
||||
def change_focus(newfocus):
|
||||
|
||||
if newfocus is None:
|
||||
widget = None
|
||||
else:
|
||||
widget = newfocus.widget
|
||||
|
||||
current = get_focused()
|
||||
|
||||
# Nothing to do.
|
||||
if current is widget:
|
||||
return
|
||||
|
||||
if current is not None:
|
||||
current.unfocus()
|
||||
|
||||
current = widget
|
||||
if widget is not None:
|
||||
widget.focus()
|
||||
|
||||
set_focused(current)
|
||||
|
||||
# This handles mouse events, to see if they change the focus.
|
||||
def mouse_handler(ev):
|
||||
x, y = ev.pos
|
||||
|
||||
newfocus = None
|
||||
default = None
|
||||
|
||||
for f in focus_list:
|
||||
|
||||
if f.x is None:
|
||||
default = f
|
||||
continue
|
||||
|
||||
if f.x <= x <= f.x + f.w and f.y <= y <= f.y + f.h:
|
||||
newfocus = f
|
||||
break
|
||||
else:
|
||||
newfocus = default
|
||||
|
||||
change_focus(newfocus)
|
||||
|
||||
|
||||
# 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:
|
||||
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 == current:
|
||||
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:
|
||||
change_focus(new_focus)
|
||||
|
||||
# And, we're done.
|
||||
|
||||
|
||||
|
||||
def key_handler(ev):
|
||||
|
||||
if renpy.display.behavior.map_event(ev, 'focus_right'):
|
||||
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'):
|
||||
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'):
|
||||
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'):
|
||||
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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# This handles pygame events that may change focus.
|
||||
def event_handler(ev):
|
||||
|
||||
if ev.type in (MOUSEMOTION, MOUSEBUTTONUP, MOUSEBUTTONDOWN):
|
||||
mouse_handler(ev)
|
||||
|
||||
key_handler(ev)
|
||||
@@ -1,576 +0,0 @@
|
||||
# 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
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
# 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
|
||||
|
||||
# Returns the maximum size of the cache, after which we start
|
||||
# tossing things out.
|
||||
def cache_limit(self):
|
||||
return renpy.config.image_cache_size * renpy.config.screen_width * renpy.config.screen_height
|
||||
|
||||
# Increments time, and clears the list of images to be
|
||||
# preloaded.
|
||||
def tick(self):
|
||||
self.time += 1
|
||||
self.preloads = [ ]
|
||||
self.first_preload_in_tick = True
|
||||
self.size_of_current_generation = 0
|
||||
|
||||
# Called to report that a given image would like to be preloaded.
|
||||
def preload_image(self, image):
|
||||
self.preloads.append(image)
|
||||
|
||||
# Do we need to preload an image?
|
||||
def needs_preload(self):
|
||||
return (self.preloads and True) or self.first_preload_in_tick
|
||||
|
||||
# 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):
|
||||
|
||||
if not isinstance(image, ImageBase):
|
||||
raise Exception("Expected an image of some sort, but got something else.")
|
||||
|
||||
if image in self.cache:
|
||||
ce = self.cache[image]
|
||||
|
||||
if ce.time == self.time:
|
||||
return ce.surf
|
||||
else:
|
||||
ce = CacheEntry(image, image.load())
|
||||
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:
|
||||
print "IC Added", ce.what
|
||||
|
||||
|
||||
# Move it into the current generation.
|
||||
ce.time = self.time
|
||||
self.size_of_current_generation += ce.size
|
||||
|
||||
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:
|
||||
print "IC Removed", ce.what
|
||||
|
||||
# Calling this cleans out the image cache if it has gotten too large.
|
||||
def cleanout(self):
|
||||
cache_limit = self.cache_limit()
|
||||
|
||||
# If we're within the limit, return.
|
||||
if self.total_cache_size <= cache_limit:
|
||||
return
|
||||
|
||||
# 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.
|
||||
|
||||
# A list of time, cache_entry pairs.
|
||||
ace = [ (ce.time, ce) for ce in self.cache.itervalues() ]
|
||||
ace.sort()
|
||||
|
||||
while ace and self.total_cache_size > cache_limit:
|
||||
|
||||
time, ce = ace.pop(0)
|
||||
|
||||
if time == self.time:
|
||||
# If we're bigger than the limit, and there's nothing
|
||||
# to remove, we should stop the preloading right away.
|
||||
|
||||
self.preloads = [ ]
|
||||
break
|
||||
|
||||
|
||||
# Otherwise, kill off the given cache entry.
|
||||
self.kill(ce)
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
print "IC is:", self.cache.keys()
|
||||
print "IC size:", self.total_cache_size, "/", cache_limit
|
||||
|
||||
|
||||
# This actually performs preloading.
|
||||
def preload(self):
|
||||
|
||||
if self.first_preload_in_tick:
|
||||
self.first_preload_in_tick = False
|
||||
|
||||
# Triage into stuff that's already in the cache and should
|
||||
# be kept there, and stuff that isn't there already.
|
||||
|
||||
new_preloads = [ ]
|
||||
|
||||
for i in self.preloads:
|
||||
if i in self.cache:
|
||||
self.get(i)
|
||||
else:
|
||||
new_preloads.append(i)
|
||||
|
||||
self.preloads = new_preloads
|
||||
|
||||
# Clean out the cache.
|
||||
self.cleanout()
|
||||
|
||||
# Return after doing said triage.
|
||||
return
|
||||
|
||||
# Otherwise, new_preloads contains things that aren't in the
|
||||
# cache already. So load one of them into the cache, maybe.
|
||||
|
||||
cache_limit = self.cache_limit()
|
||||
|
||||
# If the size of the current generation is bigger than the
|
||||
# total cache size, stop preloading.
|
||||
if self.size_of_current_generation > cache_limit:
|
||||
self.preloads = [ ]
|
||||
return
|
||||
|
||||
# Otherwise, preload the next image.
|
||||
image = self.preloads.pop(0)
|
||||
self.get(image)
|
||||
|
||||
# And, we're done.
|
||||
self.cleanout()
|
||||
|
||||
cache = Cache()
|
||||
|
||||
|
||||
class ImageBase(renpy.display.core.Displayable):
|
||||
"""
|
||||
This is the base class for all of the various kinds of images that
|
||||
we can possibly have.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **properties):
|
||||
|
||||
if 'style' not in properties:
|
||||
properties = properties.copy()
|
||||
properties['style'] = 'image_placement'
|
||||
|
||||
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):
|
||||
im = cache.get(self)
|
||||
w, h = im.get_size()
|
||||
rv = renpy.display.render.Render(w, h)
|
||||
rv.blit(im, (0, 0))
|
||||
return rv
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def predict(self, callback):
|
||||
callback(self)
|
||||
|
||||
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 load(self):
|
||||
im = pygame.image.load(renpy.loader.load(self.filename), self.filename)
|
||||
|
||||
if im.get_flags() & SRCALPHA:
|
||||
im = im.convert_alpha()
|
||||
else:
|
||||
im = im.convert()
|
||||
|
||||
return im
|
||||
|
||||
class Composite(ImageBase):
|
||||
"""
|
||||
This image manipulator composites one or more images together.
|
||||
"""
|
||||
|
||||
def __init__(self, size, *args, **properties):
|
||||
"""
|
||||
The first argument that this takes is size, which is either the
|
||||
desired size of the image (in pixels), or None to indicate that
|
||||
the size should be
|
||||
|
||||
This takes an even number of position arguments. Odd numbered
|
||||
(starting the count with 1) arguments are positions, which
|
||||
give the position of the image, in pixels, with the origin in
|
||||
the upper-left corner of the image. The even-numbered
|
||||
arguments give the images (image manipulators) that will be
|
||||
composited in those positions. The images are composited in
|
||||
bottom-to-top order.
|
||||
|
||||
@param size: If given, this will be the size of the new
|
||||
image. Otherwise, the size will be the same as that of the
|
||||
first image.
|
||||
"""
|
||||
|
||||
super(Composite, self).__init__(size, *args, **properties)
|
||||
|
||||
if len(args) % 2 != 0:
|
||||
raise Exception("Composite requires an even number of arguments.")
|
||||
|
||||
self.size = size
|
||||
self.positions = args[0::2]
|
||||
self.images = [ image(i) for i in args[1::2] ]
|
||||
|
||||
def load(self):
|
||||
|
||||
if self.size:
|
||||
size = self.size
|
||||
else:
|
||||
size = cache.get(self.images[0]).get_size()
|
||||
|
||||
rv = pygame.Surface(size, 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
for pos, im in zip(self.positions, self.images):
|
||||
rv.blit(cache.get(im), pos)
|
||||
|
||||
return rv
|
||||
|
||||
class FrameImage(ImageBase):
|
||||
"""
|
||||
This is an image that implements a frame with a given size. Instances
|
||||
of this are used by the frame object to return a new frame when
|
||||
such a new frame is needed.
|
||||
"""
|
||||
|
||||
def __init__(self, im, xborder, yborder, width, height):
|
||||
"""
|
||||
@param image: The image that will be used as the base of this
|
||||
frame.
|
||||
|
||||
@param xborder: The number of pixels in the x direction to use as
|
||||
a border.
|
||||
|
||||
@param yborder: The number of pixels in the y direction to use as
|
||||
a border.
|
||||
|
||||
@param width: The width we are being rendered at.
|
||||
|
||||
@param height: The height we are being rendered at.
|
||||
"""
|
||||
|
||||
im = image(im)
|
||||
|
||||
super(FrameImage, self).__init__(im, xborder, yborder, width, height)
|
||||
|
||||
self.image = im
|
||||
self.xborder = xborder
|
||||
self.yborder = yborder
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def load(self):
|
||||
|
||||
dw = self.width
|
||||
dh = self.height
|
||||
|
||||
dest = pygame.Surface((dw, dh), 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
source = cache.get(self.image)
|
||||
sw, sh = source.get_size()
|
||||
|
||||
def draw(x0, x1, y0, y1):
|
||||
|
||||
# Quick exit.
|
||||
if x0 == x1 or y0 == y1:
|
||||
return
|
||||
|
||||
# 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
|
||||
|
||||
# Compute sizes.
|
||||
srcsize = (sx1 - sx0, sy1 - sy0)
|
||||
dstsize = (dx1 - dx0, dy1 - dy0)
|
||||
|
||||
# Get a subsurface.
|
||||
surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
|
||||
|
||||
# Scale if we have to.
|
||||
if dstsize != srcsize:
|
||||
surf = pygame.transform.scale(surf, dstsize)
|
||||
|
||||
# Blit.
|
||||
dest.blit(surf, (dx0, dy0))
|
||||
|
||||
xb = self.xborder
|
||||
yb = self.yborder
|
||||
|
||||
# Top row.
|
||||
draw(0, xb, 0, yb)
|
||||
draw(xb, -xb, 0, yb)
|
||||
draw(-xb, 0, 0, yb)
|
||||
|
||||
# Middle row.
|
||||
draw(0, xb, yb, -yb)
|
||||
draw(xb, -xb, yb, -yb)
|
||||
draw(-xb, 0, yb, -yb)
|
||||
|
||||
# Bottom row.
|
||||
draw(0, xb, -yb, 0)
|
||||
draw(xb, -xb, -yb, 0)
|
||||
draw(-xb, 0, -yb, 0)
|
||||
|
||||
# And, finish up.
|
||||
return dest
|
||||
|
||||
class SolidImage(ImageBase):
|
||||
"""
|
||||
This is an image that is a solid rectangle with a given size. It's
|
||||
used to implement Solid.
|
||||
"""
|
||||
|
||||
def __init__(self, color, width, height):
|
||||
super(SolidImage, self).__init__(color, width, height)
|
||||
self.color = color
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def load(self):
|
||||
|
||||
rv = pygame.Surface((self.width, self.height), 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
rv.fill(self.color)
|
||||
|
||||
return rv
|
||||
|
||||
class Scale(ImageBase):
|
||||
"""
|
||||
This is an image manipulator that scales another image manipulator
|
||||
to the specified width and height. This scalling is unfiltered, so
|
||||
you can expect your image to look a bit jagged.
|
||||
"""
|
||||
|
||||
def __init__(self, im, width, height):
|
||||
|
||||
im = image(im)
|
||||
super(Scale, self).__init__(im, width, height)
|
||||
|
||||
self.image = im
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def load(self):
|
||||
return pygame.transform.scale(cache.get(self.image),
|
||||
(self.width, self.height))
|
||||
|
||||
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):
|
||||
"""
|
||||
@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)
|
||||
|
||||
self.image = im
|
||||
self.angle = angle
|
||||
self.zoom = zoom
|
||||
|
||||
def load(self):
|
||||
|
||||
return pygame.transform.rotozoom(cache.get(self.image),
|
||||
self.angle, self.zoom)
|
||||
|
||||
|
||||
class Crop(ImageBase):
|
||||
"""
|
||||
This crops the image that is its child.
|
||||
"""
|
||||
|
||||
def __init__(self, im, x, y, w, h):
|
||||
|
||||
im = image(im)
|
||||
|
||||
super(Crop, self).__init__(im, x, y, w, h)
|
||||
|
||||
self.image = im
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.w = w
|
||||
self.h = h
|
||||
|
||||
def load(self):
|
||||
return cache.get(self.image).subsurface((self.x, self.y,
|
||||
self.w, self.h))
|
||||
|
||||
|
||||
def image(arg, **properties):
|
||||
"""
|
||||
This takes as input one of a number of ways of specifying an
|
||||
image, and returns the Displayable image object that has been so
|
||||
specified. Specifically, this can take as input:
|
||||
|
||||
<ul>
|
||||
<li> An image object. In that case, it's returned unchanged.</li>
|
||||
<li> A string. If a string is given, then the string is interpreted
|
||||
as a filename, and what is returned is an im.Image object, which
|
||||
loads the image from disk.</li>
|
||||
<li> A tuple. If this is the case, then what is returned is an
|
||||
im.Composite object, which aligns the upper-left corner of all
|
||||
of the images supplied as arguments. </li>
|
||||
</ul>
|
||||
"""
|
||||
|
||||
if isinstance(arg, ImageBase):
|
||||
return arg
|
||||
|
||||
elif isinstance(arg, basestring):
|
||||
return Image(arg, **properties)
|
||||
|
||||
elif isinstance(arg, tuple):
|
||||
params = [ ]
|
||||
|
||||
for i in arg:
|
||||
params.append((0, 0))
|
||||
params.append(i)
|
||||
|
||||
return Composite(None, *params)
|
||||
|
||||
elif 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.
|
||||
"""
|
||||
|
||||
return cache.get(image(fn))
|
||||
+396
-57
@@ -1,14 +1,205 @@
|
||||
# This file contains some miscellanious 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
|
||||
from renpy.display.render import render
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
Image = renpy.display.im.image
|
||||
class ImageCache(object):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# A monotonically increasing time.
|
||||
self.time = 0
|
||||
|
||||
# A map from image filename to surface.
|
||||
self.surface_map = { }
|
||||
|
||||
# A map from image filename to last access time.
|
||||
self.time_map = { }
|
||||
|
||||
# The list of things we want to preload.
|
||||
self.preloads = [ ]
|
||||
|
||||
|
||||
def tick(self):
|
||||
self.time += 1
|
||||
self.preloads = [ ]
|
||||
|
||||
def really_load_image(self, fn):
|
||||
"""
|
||||
This is called by load_image, and does the actual loading of
|
||||
images. This may be a load of an image, or perhaps the
|
||||
compositing of images if fn is a tuple.
|
||||
"""
|
||||
|
||||
# If fn is not a single filename but a tuple, we composite the
|
||||
# elements of the tuple.
|
||||
if isinstance(fn, tuple):
|
||||
if not tuple:
|
||||
raise Exception("Trying to create a composite image from an empty tuple.")
|
||||
|
||||
base = self.load_image(fn[0])
|
||||
|
||||
rv = pygame.Surface(base.get_size(), 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
rv.blit(base, (0, 0))
|
||||
|
||||
for i in fn[1:]:
|
||||
layer = self.load_image(i)
|
||||
rv.blit(layer, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
im = pygame.image.load(renpy.loader.load(fn), fn)
|
||||
|
||||
if im.get_flags() & SRCALPHA:
|
||||
im = im.convert_alpha()
|
||||
else:
|
||||
im = im.convert()
|
||||
|
||||
return im
|
||||
|
||||
|
||||
# Forces an image load, regardless of if the cache is full or not.
|
||||
def load_image(self, fn):
|
||||
self.time_map[fn] = self.time
|
||||
|
||||
if fn in self.surface_map:
|
||||
return self.surface_map[fn]
|
||||
|
||||
if fn in self.preloads:
|
||||
self.preloads.remove(fn)
|
||||
|
||||
# iw, ih = im.get_size()
|
||||
|
||||
# surf = renpy.display.render.Render(iw, ih)
|
||||
# surf.blit(im, (0, 0))
|
||||
|
||||
im = self.really_load_image(fn)
|
||||
|
||||
self.surface_map[fn] = im
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
print "Image cache:", self.surface_map.keys()
|
||||
|
||||
return im
|
||||
|
||||
# Queues an image to be preloaded if not already loaded and there's
|
||||
# room in the cache for it.
|
||||
def preload_image(self, fn):
|
||||
self.time_map[fn] = self.time
|
||||
|
||||
if fn in self.surface_map:
|
||||
return
|
||||
|
||||
if fn not in self.preloads:
|
||||
self.preloads.append(fn)
|
||||
|
||||
|
||||
# This tries to ensure that there are n empty spaces in the image
|
||||
# cache. Returns the number of empty spaces that are actually in
|
||||
# the image cache. (A number that may be negative.)
|
||||
def clear_image_cache(self, n):
|
||||
|
||||
rv = renpy.config.image_cache_size - len(self.surface_map)
|
||||
|
||||
if rv >= n:
|
||||
return rv
|
||||
|
||||
# The number of images to remove. (This is the amount we are over
|
||||
# the cache limit + the number of images we have been requested to
|
||||
# pull.)
|
||||
num_to_remove = len(self.surface_map) - renpy.config.image_cache_size + n
|
||||
|
||||
time_files = [ (self.time_map[fn], fn) for fn in self.surface_map ]
|
||||
time_files = [ (time, fn) for time, fn in time_files if time != self.time ]
|
||||
time_files.sort()
|
||||
time_files = time_files[:num_to_remove]
|
||||
|
||||
for time, fn in time_files:
|
||||
del self.surface_map[fn]
|
||||
del self.time_map[fn]
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
print "Image cache:", self.surface_map.keys()
|
||||
|
||||
rv = renpy.config.image_cache_size - len(self.surface_map)
|
||||
|
||||
return rv
|
||||
|
||||
def needs_preload(self):
|
||||
"""
|
||||
Returns True if calling preload would do anything.
|
||||
"""
|
||||
|
||||
return self.preloads and True
|
||||
|
||||
def preload(self):
|
||||
|
||||
# If we have nothing to preload, bail early.
|
||||
if not self.preloads:
|
||||
return
|
||||
|
||||
# Try to clear up enough space for the preloads.
|
||||
avail = self.clear_image_cache(len(self.preloads))
|
||||
|
||||
if avail < 0:
|
||||
avail = 0
|
||||
|
||||
self.preloads = self.preloads[:avail]
|
||||
|
||||
# If no space is available, bail here.
|
||||
if not self.preloads:
|
||||
return
|
||||
|
||||
# Get the first thing to preload.
|
||||
fn = self.preloads[0]
|
||||
|
||||
# Actually load the image.
|
||||
try:
|
||||
self.load_image(fn)
|
||||
except:
|
||||
if renpy.config.debug:
|
||||
raise
|
||||
|
||||
cache = ImageCache()
|
||||
|
||||
class Image(renpy.display.core.Displayable):
|
||||
"""
|
||||
Returns a Displayable that is an image that is loaded from a file
|
||||
on disk.
|
||||
"""
|
||||
|
||||
def __init__(self, filename, style='image_placement', **properties):
|
||||
"""
|
||||
@param filename: The filename that the image is loaded
|
||||
from. Many common file formats are supported.
|
||||
|
||||
If the filename is not a single string but instead a tuple of
|
||||
strings, the image is considered to be"layered". In this case,
|
||||
the image will be the size of the first image in the tuple, and
|
||||
other images will be aligned with the upper-left corner of the
|
||||
image.
|
||||
"""
|
||||
|
||||
super(Image, self).__init__()
|
||||
|
||||
self.filename = filename
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def render(self, w, h, st):
|
||||
im = cache.load_image(self.filename)
|
||||
w, h = im.get_size()
|
||||
rv = renpy.display.render.Render(w, h)
|
||||
rv.blit(im, (0, 0))
|
||||
return rv
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def predict(self, callback):
|
||||
callback(self.filename)
|
||||
|
||||
class UncachedImage(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -21,13 +212,10 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
super(UncachedImage, self).__init__()
|
||||
|
||||
self.surf = pygame.image.load(file, hint)
|
||||
self.surf = self.surf.convert_alpha()
|
||||
|
||||
if scale:
|
||||
self.surf = pygame.transform.scale(self.surf, scale)
|
||||
|
||||
renpy.display.render.mutated_surface(self.surf)
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
@@ -42,7 +230,7 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
|
||||
# Should never be called, but what the hey?
|
||||
def predict(self, callback):
|
||||
return None
|
||||
callback(self.filename)
|
||||
|
||||
class ImageReference(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -70,7 +258,8 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
parameters = [ ]
|
||||
|
||||
def error(msg):
|
||||
self.target = renpy.display.text.Text(msg, color=(255, 0, 0, 255))
|
||||
self.target = renpy.display.text.Text(msg,
|
||||
color=(255, 0, 0, 255))
|
||||
|
||||
if renpy.config.debug:
|
||||
raise Exception(msg)
|
||||
@@ -100,6 +289,7 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if not hasattr(self, 'target'):
|
||||
self.find_target()
|
||||
|
||||
@@ -110,12 +300,6 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
self.find_target()
|
||||
|
||||
return self.target.get_placement()
|
||||
|
||||
def predict(self, callback):
|
||||
if not hasattr(self, 'target'):
|
||||
self.find_target()
|
||||
|
||||
self.target.predict(callback)
|
||||
|
||||
|
||||
class Solid(renpy.display.core.Displayable):
|
||||
@@ -127,8 +311,7 @@ class Solid(renpy.display.core.Displayable):
|
||||
|
||||
def __init__(self, color):
|
||||
"""
|
||||
@param color: An RGBA tuple, giving the color that the display
|
||||
will be filled with.
|
||||
@param color: An RGBA tuple, giving the color that the display will be filled with.
|
||||
"""
|
||||
|
||||
super(Solid, self).__init__()
|
||||
@@ -136,11 +319,10 @@ class Solid(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
si = renpy.display.im.SolidImage(self.color,
|
||||
width,
|
||||
height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.fill(self.color)
|
||||
|
||||
return render(si, width, height, st)
|
||||
return rv
|
||||
|
||||
class Frame(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -154,10 +336,11 @@ class Frame(renpy.display.core.Displayable):
|
||||
the center of the image is scaled in both x and y directions.
|
||||
"""
|
||||
|
||||
def __init__(self, image, xborder, yborder):
|
||||
nosave = [ 'cache' ]
|
||||
|
||||
def __init__(self, filename, xborder, yborder):
|
||||
"""
|
||||
@param image: The image (which may be a filename or image
|
||||
object) that will be scaled.
|
||||
@param filename: The file that the original image will be read from.
|
||||
|
||||
@param xborder: The number of pixels in the x direction to use as
|
||||
a border.
|
||||
@@ -165,29 +348,108 @@ class Frame(renpy.display.core.Displayable):
|
||||
@param yborder: The number of pixels in the y direction to use as
|
||||
a border.
|
||||
|
||||
For better performance, have the image share a dimension
|
||||
For better performance, have the image file share a dimension
|
||||
length in common with the size the frame will be rendered
|
||||
at. We detect this and avoid scaling if possible.
|
||||
"""
|
||||
|
||||
super(Frame, self).__init__()
|
||||
|
||||
self.image = Image(image)
|
||||
self.filename = filename
|
||||
self.xborder = xborder
|
||||
self.yborder = yborder
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
fi = renpy.display.im.FrameImage(self.image,
|
||||
self.xborder,
|
||||
self.yborder,
|
||||
width,
|
||||
height)
|
||||
|
||||
return render(fi, width, height, st)
|
||||
if hasattr(self, 'cache'):
|
||||
if self.cache.get_size() == (width, height):
|
||||
return self.cache
|
||||
|
||||
dest = renpy.display.render.Render(width, height)
|
||||
dw, dh = width, height
|
||||
|
||||
def predict(self, callback):
|
||||
self.image.predict(callback)
|
||||
source = cache.load_image(self.filename)
|
||||
sw, sh = source.get_size()
|
||||
|
||||
def draw(x0, x1, y0, y1):
|
||||
|
||||
# Quick exit.
|
||||
if x0 == x1 or y0 == y1:
|
||||
return
|
||||
|
||||
# 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
|
||||
|
||||
# Compute sizes.
|
||||
srcsize = (sx1 - sx0, sy1 - sy0)
|
||||
dstsize = (dx1 - dx0, dy1 - dy0)
|
||||
|
||||
# Get a subsurface.
|
||||
surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
|
||||
|
||||
# Scale if we have to.
|
||||
if dstsize != srcsize:
|
||||
surf = pygame.transform.scale(surf, dstsize)
|
||||
|
||||
# Blit.
|
||||
dest.blit(surf, (dx0, dy0))
|
||||
|
||||
xb = self.xborder
|
||||
yb = self.yborder
|
||||
|
||||
# Top row.
|
||||
draw(0, xb, 0, yb)
|
||||
draw(xb, -xb, 0, yb)
|
||||
draw(-xb, 0, 0, yb)
|
||||
|
||||
# Middle row.
|
||||
draw(0, xb, yb, -yb)
|
||||
draw(xb, -xb, yb, -yb)
|
||||
draw(-xb, 0, yb, -yb)
|
||||
|
||||
# Bottom row.
|
||||
draw(0, xb, -yb, 0)
|
||||
draw(xb, -xb, -yb, 0)
|
||||
draw(-xb, 0, -yb, 0)
|
||||
|
||||
# And, finish up.
|
||||
self.cache = dest
|
||||
return dest
|
||||
|
||||
class Animation(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -205,7 +467,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
animation will restart after the final delay time.
|
||||
"""
|
||||
|
||||
super(Animation, self).__init__(style='image_placement')
|
||||
super(Animation, self).__init__()
|
||||
|
||||
self.images = [ ]
|
||||
self.delays = [ ]
|
||||
@@ -213,7 +475,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
for i, arg in enumerate(args):
|
||||
|
||||
if i % 2 == 0:
|
||||
self.images.append(Image(arg))
|
||||
self.images.append(arg)
|
||||
else:
|
||||
self.delays.append(arg)
|
||||
|
||||
@@ -228,7 +490,7 @@ class Animation(renpy.display.core.Displayable):
|
||||
if t < delay:
|
||||
renpy.display.render.redraw(self, delay - t)
|
||||
|
||||
im = render(image, width, height, st)
|
||||
im = cache.load_image(image)
|
||||
width, height = im.get_size()
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(im, (0, 0))
|
||||
@@ -240,21 +502,102 @@ class Animation(renpy.display.core.Displayable):
|
||||
|
||||
def predict(self, callback):
|
||||
for i in self.images:
|
||||
i.predict(callback)
|
||||
callback(i)
|
||||
|
||||
def get_placement(self):
|
||||
return renpy.game.style.image_placement
|
||||
|
||||
|
||||
class ImageButton(renpy.display.behavior.Button):
|
||||
class ImageMap(renpy.display.core.Displayable):
|
||||
"""
|
||||
Used to implement the guts of an image button.
|
||||
The displayable that implements renpy.imagemap.
|
||||
"""
|
||||
|
||||
def __init__(self, idle_image, hover_image,
|
||||
style='image_button',
|
||||
|
||||
def __init__(self, ground, selected, hotspots, unselected=None,
|
||||
style='imagemap', **properties):
|
||||
|
||||
super(ImageMap, self).__init__()
|
||||
|
||||
self.ground = ground
|
||||
self.selected = selected
|
||||
self.hotspots = hotspots
|
||||
|
||||
if not unselected:
|
||||
self.unselected = self.ground
|
||||
else:
|
||||
self.unselected = unselected
|
||||
|
||||
self.active = None
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
# This doesn't do anything quite yet.
|
||||
def predict(self, callback):
|
||||
callback(self.ground)
|
||||
callback(self.selected)
|
||||
callback(self.unselected)
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
ground = cache.load_image(self.ground)
|
||||
selected = cache.load_image(self.selected)
|
||||
unselected = cache.load_image(self.unselected)
|
||||
|
||||
width, height = ground.get_size()
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(ground, (0, 0))
|
||||
|
||||
for i, hotspot in enumerate(self.hotspots):
|
||||
|
||||
x0, y0, x1, y1, result = hotspot
|
||||
|
||||
if i == self.active:
|
||||
source = selected
|
||||
else:
|
||||
source = unselected
|
||||
|
||||
subsurface = source.subsurface((x0, y0, x1-x0, y1-y0))
|
||||
rv.blit(subsurface, (x0, y0))
|
||||
|
||||
return rv
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
old_active = self.active
|
||||
active = None
|
||||
|
||||
for i, (x0, y0, x1, y1, result) in enumerate(self.hotspots):
|
||||
if x >= x0 and x <= x1 and y >= y0 and y <= y1:
|
||||
active = i
|
||||
break
|
||||
|
||||
# result stays set.
|
||||
|
||||
if old_active != active:
|
||||
self.active = active
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
if active is not None:
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if active is None:
|
||||
return None
|
||||
|
||||
if renpy.display.behavior.map_event(ev, "imagemap_select"):
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
class ImageButton(renpy.display.behavior.Button):
|
||||
|
||||
def __init__(self, idle_image, hover_image, style='image_button',
|
||||
image_style='image_button_image',
|
||||
clicked=None, hovered=None, **properties):
|
||||
clicked=None, hovered=None):
|
||||
|
||||
self.idle_image = Image(idle_image, style=image_style)
|
||||
self.idle_image.style.set_prefix("idle_")
|
||||
@@ -264,18 +607,14 @@ class ImageButton(renpy.display.behavior.Button):
|
||||
super(ImageButton, self).__init__(self.idle_image,
|
||||
style=style,
|
||||
clicked=clicked,
|
||||
hovered=hovered,
|
||||
**properties)
|
||||
|
||||
def predict(self, callback):
|
||||
self.idle_image.predict(callback)
|
||||
self.hover_image.predict(callback)
|
||||
hovered=hovered)
|
||||
|
||||
def focus(self, default=False):
|
||||
self.child = self.hover_image
|
||||
super(ImageButton, self).focus(default=default)
|
||||
|
||||
def unfocus(self):
|
||||
self.child = self.idle_image
|
||||
super(ImageButton, self).unfocus()
|
||||
def set_hover(self, hover):
|
||||
super(ImageButton, self).set_hover(hover)
|
||||
|
||||
if hover:
|
||||
self.child = self.hover_image
|
||||
else:
|
||||
self.child = self.idle_image
|
||||
|
||||
|
||||
+16
-24
@@ -27,7 +27,9 @@ class Null(renpy.display.core.Displayable):
|
||||
"""
|
||||
|
||||
def __init__(self, width=0, height=0, style='default', **properties):
|
||||
super(Null, self).__init__(style=style, **properties)
|
||||
super(Null, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
@@ -35,12 +37,7 @@ class Null(renpy.display.core.Displayable):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
rv = renpy.display.render.Render(self.width, self.height)
|
||||
|
||||
if self.focusable:
|
||||
rv.add_focus(self, None, None, None, None, None)
|
||||
|
||||
return rv
|
||||
return renpy.display.render.Render(self.width, self.height)
|
||||
|
||||
|
||||
class Container(renpy.display.core.Displayable):
|
||||
@@ -63,24 +60,16 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **properties):
|
||||
def __init__(self, *args):
|
||||
|
||||
super(Container, self).__init__()
|
||||
|
||||
self.children = []
|
||||
self.child = None
|
||||
|
||||
for i in args:
|
||||
self.add(i)
|
||||
|
||||
super(Container, self).__init__(**properties)
|
||||
|
||||
|
||||
def find_focusable(self, callback, focus_name):
|
||||
super(Container, self).find_focusable(callback, focus_name)
|
||||
|
||||
for i in self.children:
|
||||
i.find_focusable(callback, self.focus_name or focus_name)
|
||||
|
||||
|
||||
def set_style_prefix(self, prefix):
|
||||
super(Container, self).set_style_prefix(prefix)
|
||||
|
||||
@@ -143,8 +132,6 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
def predict(self, callback):
|
||||
|
||||
super(Container, self).predict(callback)
|
||||
|
||||
for i in self.children:
|
||||
i.predict(callback)
|
||||
|
||||
@@ -163,7 +150,8 @@ class Fixed(Container):
|
||||
"""
|
||||
|
||||
def __init__(self, style='default', **properties):
|
||||
super(Fixed, self).__init__(style=style, **properties)
|
||||
super(Fixed, self).__init__()
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.times = [ ]
|
||||
|
||||
def add(self, widget, time=None):
|
||||
@@ -226,7 +214,9 @@ class Position(Container):
|
||||
child of this widget is placed.
|
||||
"""
|
||||
|
||||
super(Position, self).__init__(style=style, **properties)
|
||||
super(Position, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.add(child)
|
||||
|
||||
def render(self, width, height, st):
|
||||
@@ -466,15 +456,17 @@ class Window(Container):
|
||||
|
||||
def __init__(self, child, style='window', **properties):
|
||||
|
||||
super(Window, self).__init__(style=style, **properties)
|
||||
super(Window, self).__init__()
|
||||
|
||||
self.add(child)
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
# save some typing.
|
||||
# save typing and screen space.
|
||||
style = self.style
|
||||
|
||||
xminimum = scale(style.xminimum, width)
|
||||
|
||||
+61
-202
@@ -1,11 +1,6 @@
|
||||
# Render lifespan.
|
||||
# Rules for renders: Every widget creates its own Render, even if all
|
||||
# it does is blit that render somewhere else.
|
||||
|
||||
# A render is alive when it is first created. It stays alive on subsequent
|
||||
# styles if it is not killed and it is used. It can be killed either
|
||||
# due to lack of use by the end of a cycle or because it was killed between
|
||||
# cycles due to a timeout.
|
||||
|
||||
import sets
|
||||
import time
|
||||
import renpy
|
||||
import pygame
|
||||
@@ -28,14 +23,11 @@ class SolidCache(object):
|
||||
self.color = color
|
||||
|
||||
if color[3] == 255:
|
||||
surf = pygame.Surface(size, 0,
|
||||
renpy.game.interface.display.window)
|
||||
surf = pygame.Surface(size, 0, renpy.game.interface.display.window)
|
||||
else:
|
||||
surf = pygame.Surface(size, 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
mutated_surface(surf)
|
||||
|
||||
surf.fill(color)
|
||||
|
||||
self.cached = surf
|
||||
@@ -47,15 +39,18 @@ solid_cache = SolidCache()
|
||||
## if any widget producing a render is redrawn, all instances of that
|
||||
## render are killed, and so the entire thing is redrawn.
|
||||
|
||||
# Renders of a given widget.
|
||||
widget_renders = { }
|
||||
|
||||
# Renders that have been used during the current rendering pass.
|
||||
new_renders = { }
|
||||
|
||||
# Renders that were used on the old rendering pass.
|
||||
old_renders = { }
|
||||
|
||||
# The set of surfaces that are mutated (that is, can change their
|
||||
# The set of surfaces that are mutable (that is, can change their
|
||||
# contents.)
|
||||
mutated_surfaces = { }
|
||||
mutable_surfaces = { }
|
||||
|
||||
def render(widget, width, height, st):
|
||||
"""
|
||||
@@ -65,20 +60,20 @@ def render(widget, width, height, st):
|
||||
if (widget, width, height) in old_renders:
|
||||
rv = old_renders[widget, width, height]
|
||||
|
||||
# assert (widget, width, height) in rv.render_of
|
||||
# assert not rv.dead
|
||||
assert widget in rv.widgets
|
||||
|
||||
rv.keep_alive()
|
||||
rv.add_to_new_renders()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
rv = widget.render(width, height, st)
|
||||
|
||||
rv.render_of.append((widget, width, height))
|
||||
rv.req_width = width
|
||||
rv.req_height = height
|
||||
|
||||
old_renders[widget, width, height] = rv
|
||||
rv.widgets.append(widget)
|
||||
new_renders[widget, width, height] = rv
|
||||
widget_renders.setdefault(widget, []).append(rv)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -94,7 +89,6 @@ def process_redraws():
|
||||
redraw_queue.sort()
|
||||
|
||||
i = 0
|
||||
dead_widgets = sets.Set()
|
||||
now = time.time()
|
||||
|
||||
for when, widget in redraw_queue:
|
||||
@@ -104,17 +98,14 @@ def process_redraws():
|
||||
|
||||
i += 1
|
||||
|
||||
dead_widgets.add(widget)
|
||||
if widget in widget_renders:
|
||||
for r in widget_renders[widget]:
|
||||
r.remove_from_old_renders()
|
||||
|
||||
if not dead_widgets:
|
||||
if i == 0:
|
||||
return False
|
||||
|
||||
redraw_queue = redraw_queue[i:]
|
||||
|
||||
for (widget, width, height), render in old_renders.items():
|
||||
if widget in dead_widgets:
|
||||
render.kill()
|
||||
|
||||
return True
|
||||
|
||||
def redraw(widget, when):
|
||||
@@ -127,36 +118,21 @@ def redraw(widget, when):
|
||||
|
||||
def render_screen(widget, width, height, st):
|
||||
|
||||
global widget_renders
|
||||
global redraw_queue
|
||||
global old_renders
|
||||
global new_renders
|
||||
global mutated_surfaces
|
||||
global mutable_surfaces
|
||||
|
||||
mutated_surfaces = { }
|
||||
new_renders = { }
|
||||
widget_renders = { }
|
||||
redraw_queue = [ ]
|
||||
mutable_surfaces = { }
|
||||
|
||||
rv = render(widget, width, height, st)
|
||||
|
||||
# Renders that are in the old set but not the new one die here.
|
||||
old_render_set = sets.Set(old_renders.itervalues())
|
||||
new_render_set = sets.Set(new_renders.itervalues())
|
||||
old_renders = new_renders
|
||||
|
||||
dead_render_set = old_render_set - new_render_set
|
||||
|
||||
for r in dead_render_set:
|
||||
r.kill()
|
||||
|
||||
old_renders.update(new_renders)
|
||||
new_renders.clear()
|
||||
|
||||
# Figure out which widgets are still alive.
|
||||
live_widgets = sets.Set()
|
||||
for widget, height, width in old_renders:
|
||||
live_widgets.add(widget)
|
||||
|
||||
# Filter dead widgets from the redraw queue.
|
||||
redraw_queue = [ (when, widget) for when, widget in redraw_queue if
|
||||
widget in live_widgets ]
|
||||
|
||||
return rv
|
||||
|
||||
old_blits = [ ]
|
||||
@@ -203,8 +179,8 @@ def compute_clip(source):
|
||||
# If the two are the same.
|
||||
if b0 == b1:
|
||||
|
||||
# Only add if the surface is mutated.
|
||||
if b0[0] in mutated_surfaces:
|
||||
# Only add if the surface is mutable.
|
||||
if b0[0] in mutable_surfaces:
|
||||
changes.append(b0)
|
||||
|
||||
i0 += 1
|
||||
@@ -264,19 +240,17 @@ def screen_blit(source, full=False):
|
||||
source.blit_to(screen, 0, 0)
|
||||
|
||||
screen.set_clip()
|
||||
|
||||
|
||||
return cliprect
|
||||
|
||||
|
||||
|
||||
def mutated_surface(surf):
|
||||
def mutable_surface(surf):
|
||||
"""
|
||||
Called to indicate that a pygame surface has been mutated. This also
|
||||
should be called each time a new pygame surface is created.
|
||||
Called to indicate that a pygame surface is mutable.
|
||||
"""
|
||||
|
||||
mutated_surfaces[id(surf)] = True
|
||||
|
||||
mutable_surfaces[id(surf)] = True
|
||||
|
||||
class Render(object):
|
||||
"""
|
||||
@@ -296,23 +270,18 @@ class Render(object):
|
||||
widget, then this is the widget it corresponds to.
|
||||
"""
|
||||
|
||||
# Just for safety's sake.
|
||||
self.dead = False
|
||||
self.widgets = [ ]
|
||||
|
||||
# A list of widget, width, height, corresponding to the
|
||||
# entries in old_renders that this render is in.
|
||||
self.render_of = [ ]
|
||||
|
||||
# The width and height of this render.
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
# The parents of this render.
|
||||
self.req_width = width
|
||||
self.req_height = height
|
||||
|
||||
self.parents = [ ]
|
||||
|
||||
self.blittables = [ ]
|
||||
self.children = [ ]
|
||||
self.depends = [ ]
|
||||
|
||||
# A pygame surface holding this Render, if one exists.
|
||||
self.surface = None
|
||||
@@ -320,98 +289,41 @@ class Render(object):
|
||||
|
||||
self.subsurfaces = { }
|
||||
|
||||
# The list of focusable widgets collected from this render
|
||||
# and the children of this render. A list of (widget, arg, x, y, w, h,)
|
||||
self.focuses = [ ]
|
||||
def add_to_new_renders(self):
|
||||
|
||||
# def __del__(self):
|
||||
# Render.renders -= 1
|
||||
# print "Render del", Render.renders, Render.liverenders, self
|
||||
|
||||
def keep_alive(self):
|
||||
|
||||
# assert not self.dead
|
||||
|
||||
for widget, width, height in self.render_of:
|
||||
new_renders[widget, width, height] = self
|
||||
for w in self.widgets:
|
||||
widget_renders.setdefault(w, []).append(self)
|
||||
new_renders[w, self.req_width, self.req_height] = self
|
||||
|
||||
for i in self.children:
|
||||
i.add_to_new_renders()
|
||||
|
||||
# assert self in i.parents
|
||||
def remove_from_old_renders(self):
|
||||
|
||||
# assert not i.dead
|
||||
for w in self.widgets:
|
||||
# We need to check, since the same widget can appear twice.
|
||||
if (w, self.req_width, self.req_height) in old_renders:
|
||||
del old_renders[w, self.req_width, self.req_height]
|
||||
|
||||
i.keep_alive()
|
||||
self.widgets = [ ]
|
||||
|
||||
def kill(self):
|
||||
"""
|
||||
Calling this marks the render and all of its parents dead. It also
|
||||
unlinks it from the tree, readying it for reclamation.
|
||||
"""
|
||||
for p in self.parents:
|
||||
p.remove_from_old_renders()
|
||||
|
||||
if self.dead:
|
||||
return
|
||||
self.parents = [ ]
|
||||
|
||||
self.dead = True
|
||||
|
||||
for widget, width, height in self.render_of:
|
||||
del old_renders[widget, width, height]
|
||||
|
||||
if (widget, width, height) in new_renders:
|
||||
del new_renders[widget, width, height]
|
||||
|
||||
parents = self.parents[:]
|
||||
children = self.children[:]
|
||||
depends = self.depends[:]
|
||||
|
||||
for p in parents:
|
||||
p.kill()
|
||||
|
||||
for c in children:
|
||||
while self in c.parents:
|
||||
c.parents.remove(self)
|
||||
|
||||
for c in depends:
|
||||
while self in c.parents:
|
||||
c.parents.remove(self)
|
||||
|
||||
# assert not self.parents
|
||||
|
||||
# for p in parents:
|
||||
# # assert p.dead
|
||||
|
||||
self.children = [ ]
|
||||
self.depends = [ ]
|
||||
|
||||
# Removes cycles.
|
||||
self.render_of = [ ]
|
||||
self.focuses = [ ]
|
||||
|
||||
def blit(self, source, (xo, yo), focus=True):
|
||||
def blit(self, source, (x, y)):
|
||||
"""
|
||||
Adds the source to the list of things that need to be blitted
|
||||
to the screen. The source should be either a pygame.Surface,
|
||||
or a Render.
|
||||
"""
|
||||
|
||||
|
||||
if isinstance(source, Render):
|
||||
# assert not source.dead
|
||||
|
||||
source.parents.append(self)
|
||||
self.children.append(source)
|
||||
|
||||
if focus and xo == 0 and yo == 0:
|
||||
self.focuses.extend(source.focuses)
|
||||
elif focus:
|
||||
for widget, arg, x, y, w, h in source.focuses:
|
||||
if x is not None:
|
||||
x += xo
|
||||
y += yo
|
||||
|
||||
self.add_focus(widget, arg, x, y, w, h)
|
||||
|
||||
self.blittables.append((xo, yo, source))
|
||||
|
||||
self.blittables.append((x, y, source))
|
||||
|
||||
|
||||
def blit_to(self, dest, x, y):
|
||||
@@ -480,11 +392,9 @@ class Render(object):
|
||||
self.surface = rv
|
||||
self.surface_alpha = alpha
|
||||
|
||||
mutated_surface(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def subsurface(self, pos, focus=False):
|
||||
def subsurface(self, pos):
|
||||
"""
|
||||
Returns a subsurface of this render.
|
||||
"""
|
||||
@@ -502,24 +412,6 @@ class Render(object):
|
||||
|
||||
rv = Render(width, height)
|
||||
|
||||
if focus:
|
||||
for fwidget, farg, fx, fy, fw, fh in self.focuses:
|
||||
if fx is not None:
|
||||
fx -= x
|
||||
fx = max(fx, 0)
|
||||
fy -= y
|
||||
fy = max(fy, 0)
|
||||
|
||||
fw -= x
|
||||
fw = min(fw, width)
|
||||
fh -= y
|
||||
fh = min(fh, height)
|
||||
|
||||
if fw <= 0 or fh <= 0:
|
||||
continue
|
||||
|
||||
rv.add_focus(fwidget, farg, fx, fy, fw, fh)
|
||||
|
||||
for xo, yo, source in self.blittables:
|
||||
|
||||
# ulx, uly -- the coordinates of the upper-left hand corner of
|
||||
@@ -544,25 +436,17 @@ class Render(object):
|
||||
else:
|
||||
oy = uly
|
||||
sy = 0
|
||||
|
||||
|
||||
if ox > width or oy > height:
|
||||
continue
|
||||
|
||||
|
||||
sw, sh = source.get_size()
|
||||
|
||||
sw = min(sw - sx, width - ox)
|
||||
sh = min(sh - sy, height - oy)
|
||||
sw = min(sw - sx, width)
|
||||
sh = min(sh - sy, height)
|
||||
|
||||
if sw <= 0 or sh <= 0:
|
||||
continue
|
||||
|
||||
subsurf = source.subsurface((sx, sy, sw, sh))
|
||||
|
||||
if isinstance(subsurf, pygame.Surface):
|
||||
mutated_surface(subsurf)
|
||||
|
||||
rv.blit(subsurf, (ox, oy))
|
||||
rv.blit(source.subsurface((sx, sy, sw, sh)),
|
||||
(ox, oy))
|
||||
|
||||
|
||||
self.subsurfaces[pos] = rv
|
||||
@@ -570,36 +454,11 @@ class Render(object):
|
||||
|
||||
return rv
|
||||
|
||||
def depends_on(self, child):
|
||||
def depends_on(self, render):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
# assert not child.dead
|
||||
|
||||
self.depends.append(child)
|
||||
child.parents.append(self)
|
||||
|
||||
def add_focus(self, widget, arg=None, x=0, y=0, w=None, h=None):
|
||||
"""
|
||||
This is called to indicate a region of the screen that can be
|
||||
focused.
|
||||
|
||||
@param widget: The widget that will be focused.
|
||||
@param arg: A focus argument, which can be checked by the widget.
|
||||
|
||||
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 x is not None:
|
||||
if w is None:
|
||||
w = self.width
|
||||
|
||||
if h is None:
|
||||
h = self.height
|
||||
|
||||
self.focuses.append(renpy.display.focus.Focus(widget, arg, x, y, w, h))
|
||||
|
||||
render.parents.append(self)
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
# Function to allocate a surface.
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
import renpy
|
||||
|
||||
# def Surface(width, height):
|
||||
# """
|
||||
# Allocate a surface. Flags and depth are ignored, for compatibility
|
||||
# with pygame.Surface.
|
||||
# """
|
||||
|
||||
# return pygame.Surface((width, height), 0,
|
||||
# renpy.game.interface.display.sample_surface)
|
||||
|
||||
# class FilledSurface(object):
|
||||
|
||||
# def __init__(self, width, height, color):
|
||||
# self.width = width
|
||||
# self.height = height
|
||||
# self.color = color
|
||||
|
||||
# def blit_to(self, dest, x, y):
|
||||
# dest.set_alpha(0)
|
||||
# dest.fill(self.color, [ x, y, self.width, self.height ])
|
||||
|
||||
# We only cache a single solid... but that should be enough to handle
|
||||
# some important cases, like button and window backgrounds.
|
||||
class SolidCache(object):
|
||||
|
||||
def __init__(self):
|
||||
self.size = None
|
||||
self.color = None
|
||||
self.cached = None
|
||||
|
||||
def create(self, size, color):
|
||||
if size == self.size and color == self.color:
|
||||
return self.cached
|
||||
|
||||
self.size = size
|
||||
self.color = color
|
||||
|
||||
surf = pygame.Surface(size, 0,
|
||||
renpy.game.interface.display.sample_surface)
|
||||
|
||||
surf.fill(color)
|
||||
|
||||
self.cached = surf
|
||||
return surf
|
||||
|
||||
|
||||
solid_cache = SolidCache()
|
||||
|
||||
class Surface(object):
|
||||
"""
|
||||
This is our own surface object, which is a node in a tree in which
|
||||
all of the leaves are PyGame surfaces. It ensures that things are
|
||||
only blit to the screen once, hopefully giving a performance boost.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.blittables = [ ]
|
||||
|
||||
def blit(self, source, (x, y)):
|
||||
"""
|
||||
Adds the source surface to the list of things that need to be
|
||||
blitted to the screen. The source surface is either a
|
||||
pygame.Surface, or one of these Ren'Py Surfaces.
|
||||
"""
|
||||
|
||||
self.blittables.append((x, y, source))
|
||||
|
||||
|
||||
def blit_to(self, dest, x, y):
|
||||
"""
|
||||
This blits the children of this Surface to dest, which must be
|
||||
a pygame.Surface. The x and y parameters are the location of
|
||||
the upper-left hand corner of this surface, relative to the
|
||||
destination surface.
|
||||
"""
|
||||
|
||||
for xo, yo, source in self.blittables:
|
||||
|
||||
if isinstance(source, pygame.Surface):
|
||||
dest.blit(source, (x + xo, y + yo))
|
||||
else:
|
||||
source.blit_to(dest, x + xo, y + yo)
|
||||
|
||||
def fill(self, color):
|
||||
"""
|
||||
Fake a pygame.Surface.fill()
|
||||
"""
|
||||
|
||||
surf = solid_cache.create((self.width, self.height), color)
|
||||
|
||||
# surf = pygame.Surface((self.width, self.height), 0,
|
||||
# renpy.game.interface.display.sample_surface)
|
||||
|
||||
# surf.fill(color)
|
||||
|
||||
# surf = FilledSurface(self.width, self.height, color)
|
||||
|
||||
self.blittables.append((0, 0, surf))
|
||||
|
||||
def get_size(self):
|
||||
"""
|
||||
Returns the size of this surface, 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 pygame_surface(self, alpha=True):
|
||||
"""
|
||||
Returns a pygame surface constructed from self.
|
||||
"""
|
||||
|
||||
if alpha:
|
||||
sample = renpy.game.interface.display.sample_surface
|
||||
else:
|
||||
sample = renpy.game.interface.display.window
|
||||
|
||||
rv = pygame.Surface((self.width, self.height), 0, sample)
|
||||
|
||||
self.blit_to(rv, 0, 0)
|
||||
|
||||
return rv
|
||||
|
||||
def subsurface(self, (x, y, width, height)):
|
||||
"""
|
||||
Returns the subsurface of this surface, similar to
|
||||
pygame.Surface.subsurface
|
||||
"""
|
||||
|
||||
if x > self.width or y > self.height:
|
||||
return Surface(0, 0)
|
||||
|
||||
width = min(self.width - x, width)
|
||||
height = min(self.height - y, height)
|
||||
|
||||
rv = Surface(width, height)
|
||||
|
||||
for xo, yo, source in self.blittables:
|
||||
|
||||
# ulx, uly -- the coordinates of the upper-left hand corner of
|
||||
# the image, relative to the subsurface.
|
||||
|
||||
ulx = xo - x
|
||||
uly = yo - y
|
||||
|
||||
# ox, oy -- the offsets that the source will be blitted at.
|
||||
# sx, sy -- the offset within the subsurface at which we begin.
|
||||
|
||||
if ulx < 0:
|
||||
ox = 0
|
||||
sx = -ulx
|
||||
else:
|
||||
ox = ulx
|
||||
sx = 0
|
||||
|
||||
if uly < 0:
|
||||
oy = 0
|
||||
sy = -uly
|
||||
else:
|
||||
oy = uly
|
||||
sy = 0
|
||||
|
||||
sw, sh = source.get_size()
|
||||
|
||||
if sw - ox <= 0:
|
||||
continue
|
||||
if sh - oy <= 0:
|
||||
continue
|
||||
|
||||
sw = min(sw - sx - ox, width)
|
||||
sh = min(sh - sy - oy, height)
|
||||
|
||||
rv.blit(source.subsurface((sx, sy, sw, sh)),
|
||||
(ox, oy))
|
||||
|
||||
return rv
|
||||
+105
-360
@@ -1,114 +1,49 @@
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
import re
|
||||
import renpy
|
||||
import sys
|
||||
|
||||
_font_cache = { }
|
||||
|
||||
# TODO: Something sane if the font file can't be found.
|
||||
def get_font(fn, size, bold=False, italics=False, underline=False):
|
||||
def get_font(fn, size):
|
||||
from renpy.loader import transfn
|
||||
|
||||
if (fn, size, bold, italics, underline) in _font_cache:
|
||||
return _font_cache[(fn, size, bold, italics, underline)]
|
||||
if (fn, size) in _font_cache:
|
||||
return _font_cache[(fn, size)]
|
||||
|
||||
try:
|
||||
rv = pygame.font.Font(transfn(fn), size)
|
||||
rv.set_bold(bold)
|
||||
rv.set_italic(italics)
|
||||
except:
|
||||
rv = pygame.font.SysFont(fn, size, bold, italics)
|
||||
rv = pygame.font.SysFont(fn, size)
|
||||
|
||||
rv.set_underline(underline)
|
||||
|
||||
_font_cache[(fn, size, bold, italics, underline)] = rv
|
||||
_font_cache[(fn, size)] = rv
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def color(s):
|
||||
"""
|
||||
This function converts a hexcode into a color/alpha tuple. Leading
|
||||
# marks are ignored. Colors can be rgb or rgba, with each element having
|
||||
either one or two digits. (So the strings can be 3, 4, 6, or 8 digits long,
|
||||
not including the optional #.) A missing alpha is interpreted as 255,
|
||||
fully opaque.
|
||||
|
||||
For example, color('#123a') returns (17, 34, 51, 170), while
|
||||
color('c0c0c0') returns (192, 192, 192, 255).
|
||||
"""
|
||||
|
||||
if s[0] == '#':
|
||||
s = s[1:]
|
||||
|
||||
if len(s) == 6:
|
||||
r = int(s[0]+s[1], 16)
|
||||
g = int(s[2]+s[3], 16)
|
||||
b = int(s[4]+s[5], 16)
|
||||
a = 255
|
||||
elif len(s) == 8:
|
||||
r = int(s[0]+s[1], 16)
|
||||
g = int(s[2]+s[3], 16)
|
||||
b = int(s[4]+s[5], 16)
|
||||
a = int(s[6]+s[7], 16)
|
||||
elif len(s) == 3:
|
||||
r = int(s[0], 16) * 0x11
|
||||
g = int(s[1], 16) * 0x11
|
||||
b = int(s[2], 16) * 0x11
|
||||
a = 255
|
||||
elif len(s) == 4:
|
||||
r = int(s[0], 16) * 0x11
|
||||
g = int(s[1], 16) * 0x11
|
||||
b = int(s[2], 16) * 0x11
|
||||
a = int(s[3], 16) * 0x11
|
||||
else:
|
||||
raise Exception("Argument to color() must be 3, 4, 6, or 8 hex digits long.")
|
||||
|
||||
return (r, g, b, a)
|
||||
|
||||
class TextStyle(object):
|
||||
"""
|
||||
This is used to represent the style of text that will be displayed
|
||||
on the screen.
|
||||
"""
|
||||
|
||||
def __init__(self, source=None):
|
||||
if source is not None:
|
||||
vars(self).update(vars(source))
|
||||
|
||||
def get_font(self):
|
||||
return get_font(self.font, self.size, self.bold, self.italic, self.underline)
|
||||
|
||||
def get_ascent(self):
|
||||
return self.get_font().get_ascent()
|
||||
|
||||
def sizes(self, text):
|
||||
font = self.get_font()
|
||||
# print font.get_ascent() - font.get_descent(), font.get_height(), font.get_linesize()
|
||||
return font.size(text)[0], font.get_ascent() - font.get_descent()
|
||||
|
||||
def render(self, text, antialias, color, use_colors):
|
||||
|
||||
if use_colors and self.color:
|
||||
color = self.color
|
||||
|
||||
font = self.get_font()
|
||||
|
||||
rv = font.render(text, antialias, color)
|
||||
renpy.display.render.mutated_surface(rv)
|
||||
return rv
|
||||
|
||||
class Text(renpy.display.core.Displayable):
|
||||
"""
|
||||
A displayable that can format and display text on the screen.
|
||||
A Displayable that can display text on the screen.
|
||||
"""
|
||||
|
||||
|
||||
"""
|
||||
@ivar style: The style that is used to display the text.
|
||||
@ivar text: The text that is being displayed.
|
||||
|
||||
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_width', 'laidout_height', 'width' ]
|
||||
The following aren't serialized, but are reconstructed the first
|
||||
time this is redrawn:
|
||||
|
||||
def after_setstate(self):
|
||||
self.laidout = None
|
||||
@ivar laidout: The text, split into a list of strings where each
|
||||
string happens to be one line on the screen.
|
||||
|
||||
@ivar height: The height of the laid-out text.
|
||||
@ivar width: The width of the laid-out text.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, text, slow=False, style='default', **properties):
|
||||
"""
|
||||
@@ -121,14 +56,13 @@ class Text(renpy.display.core.Displayable):
|
||||
@param properties: Additional properties that are applied to the text.
|
||||
"""
|
||||
|
||||
|
||||
super(Text, self).__init__()
|
||||
|
||||
self.text = text
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.slow = slow
|
||||
|
||||
self.laidout = None
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
@@ -152,317 +86,130 @@ class Text(renpy.display.core.Displayable):
|
||||
"""
|
||||
This is called after this widget has been updated by
|
||||
set_text or set_style.
|
||||
"""
|
||||
|
||||
self.laidout = None
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
Space, Enter, or Click ends slow, if it's enabled.
|
||||
"""
|
||||
|
||||
if not self.slow:
|
||||
return None
|
||||
|
||||
if renpy.display.behavior.map_event(ev, "dismiss"):
|
||||
|
||||
self.slow = False
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
try:
|
||||
del self.laidout
|
||||
del self.width
|
||||
del self.height
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def layout(self, width):
|
||||
"""
|
||||
This lays out the text of this widget. It sets self.laidout,
|
||||
self.laidout_lineheights, self.laidout_width, and
|
||||
self.laidout_height.
|
||||
Called to split the text into a string with newline characters
|
||||
at line endings where wrapping has occured.
|
||||
"""
|
||||
|
||||
if self.laidout and self.width == width:
|
||||
return
|
||||
|
||||
# Set this, so caching works.
|
||||
self.width = width
|
||||
|
||||
def indent():
|
||||
if lines:
|
||||
return self.style.rest_indent
|
||||
else:
|
||||
return self.style.first_indent
|
||||
|
||||
|
||||
tsl = [ TextStyle() ]
|
||||
tsl[-1].font = self.style.font
|
||||
tsl[-1].size = self.style.size
|
||||
tsl[-1].bold = self.style.bold
|
||||
tsl[-1].italic = self.style.italic
|
||||
tsl[-1].underline = self.style.underline
|
||||
tsl[-1].color = None
|
||||
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
lines = [ ]
|
||||
line = [ ]
|
||||
pars = self.text.split('\n')
|
||||
|
||||
# The height of the current line, in pixels, not including
|
||||
# line_spacing.
|
||||
lineheight = 0
|
||||
lh = 0
|
||||
|
||||
# A list of same.
|
||||
lineheights = [ ]
|
||||
|
||||
# The width of the current line.
|
||||
linewidth = 0
|
||||
|
||||
# The maximum linewidth.
|
||||
maxwidth = 0
|
||||
|
||||
# The current text.
|
||||
cur = ""
|
||||
# The indent of the current line.
|
||||
indent = self.style.first_indent
|
||||
|
||||
|
||||
# The width, in pixels, of cur.
|
||||
curwidth = 0
|
||||
for p in pars:
|
||||
words = p.split(' ')
|
||||
|
||||
line = ""
|
||||
|
||||
# The remaining width of the line, not including the text in
|
||||
# cur.
|
||||
remwidth = width - indent()
|
||||
|
||||
for i in re.split(r'( |\{[^{]+\}|\{\{|\n)', self.text):
|
||||
|
||||
# Newline.
|
||||
if i == "\n":
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
maxwidth = max(maxwidth, linewidth + curwidth)
|
||||
cur = ""
|
||||
|
||||
lines.append(line)
|
||||
lineheights.append(lineheight)
|
||||
|
||||
line = [ ]
|
||||
linewidth = 0
|
||||
curwidth, lineheight = tsl[-1].sizes(" ")
|
||||
remwidth = width - indent()
|
||||
|
||||
continue
|
||||
|
||||
elif i == "{{":
|
||||
i = "{"
|
||||
# We want to render this like a word, so no continue.
|
||||
|
||||
elif i.startswith("{"):
|
||||
|
||||
# Are we closing a tag?
|
||||
if i.startswith("{/"):
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
cur = ""
|
||||
remwidth -= curwidth
|
||||
linewidth += curwidth
|
||||
curwidth = 0
|
||||
|
||||
tsl.pop()
|
||||
|
||||
if not tsl:
|
||||
raise Exception("Closing tag %s does not match an open tag." % i)
|
||||
for w in words:
|
||||
|
||||
# Each line must have at least one word on it.
|
||||
if not line:
|
||||
line = w
|
||||
lw, lh = font.size(line)
|
||||
maxwidth = max(maxwidth, lw)
|
||||
continue
|
||||
|
||||
# Otherwise, we're opening a new tag.
|
||||
|
||||
# Mark up any text that uses an old style.
|
||||
if cur:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
cur = ""
|
||||
remwidth -= curwidth
|
||||
linewidth += curwidth
|
||||
curwidth = 0
|
||||
|
||||
tsl.append(TextStyle(tsl[-1]))
|
||||
|
||||
if i == "{b}":
|
||||
tsl[-1].bold = True
|
||||
|
||||
elif i == "{i}":
|
||||
tsl[-1].italic = True
|
||||
|
||||
elif i == "{u}":
|
||||
tsl[-1].underline = True
|
||||
|
||||
elif i == "{plain}":
|
||||
tsl[-1].bold = False
|
||||
tsl[-1].italic = False
|
||||
tsl[-1].underline = False
|
||||
|
||||
elif i.startswith("{size"):
|
||||
|
||||
m = re.match(r'\{size=(\+|-|)(\d+)\}', i)
|
||||
|
||||
if not m:
|
||||
raise Exception('Size tag %s could not be parsed.' % i)
|
||||
|
||||
if m.group(1) == '+':
|
||||
tsl[-1].size += int(m.group(2))
|
||||
elif m.group(1) == '-':
|
||||
tsl[-1].size -= int(m.group(2))
|
||||
else:
|
||||
tsl[-1].size = int(m.group(2))
|
||||
|
||||
elif i.startswith("{color"):
|
||||
|
||||
m = re.match(r'\{color=(\#?[a-fA-F0-9]+)\}', i)
|
||||
|
||||
if not m:
|
||||
raise Exception('Color tag %s could not be parsed.' % i)
|
||||
|
||||
tsl[-1].color = color(m.group(1))
|
||||
lw, lh = font.size(line + " " + w)
|
||||
|
||||
if lw + indent < width:
|
||||
line += " " + w
|
||||
maxwidth = max(maxwidth, lw)
|
||||
else:
|
||||
raise Exception("Text tag %s was not recognized. Case and spacing matter here.")
|
||||
lines.append(line)
|
||||
line = w
|
||||
indent = self.style.rest_indent
|
||||
|
||||
continue
|
||||
|
||||
elif i == ' ':
|
||||
# Spaces always get appended to the end of a line. So they
|
||||
# will never show up at the start of a line, unless they're
|
||||
# after a newline or at the start of a string.
|
||||
|
||||
cur += i
|
||||
curwidth, lh = tsl[-1].sizes(cur)
|
||||
lineheight = max(lh, lineheight)
|
||||
|
||||
continue
|
||||
|
||||
# If we made it here, then we have normal text.
|
||||
|
||||
# We must have at least one word or something else in the
|
||||
# line before we care about wrapping.
|
||||
if not cur and not line:
|
||||
cur = i
|
||||
curwidth, lineheight = tsl[-1].sizes(cur)
|
||||
continue
|
||||
|
||||
# Should we wrap?
|
||||
curwidth, lh = tsl[-1].sizes(cur + i)
|
||||
|
||||
if curwidth > remwidth:
|
||||
line.append((TextStyle(tsl[-1]), cur))
|
||||
lines.append(line)
|
||||
|
||||
maxwidth = max(maxwidth, linewidth)
|
||||
|
||||
line = [ ]
|
||||
lineheights.append(lineheight)
|
||||
|
||||
cur = i
|
||||
curwidth, lineheight = tsl[-1].sizes(cur)
|
||||
remwidth = width - indent()
|
||||
linewidth = 0
|
||||
else:
|
||||
cur = cur + i
|
||||
lineheight = max(lh, lineheight)
|
||||
|
||||
# We're done. Let's close up.
|
||||
|
||||
if len(tsl) != 1:
|
||||
exception("A tag was left open at the end of the text.")
|
||||
|
||||
if cur:
|
||||
line.append((tsl[-1], cur))
|
||||
maxwidth = max(maxwidth, curwidth)
|
||||
|
||||
if line:
|
||||
lines.append(line)
|
||||
lineheights.append(lineheight)
|
||||
|
||||
|
||||
self.laidout = lines
|
||||
self.laidout_lineheights = lineheights
|
||||
self.laidout_width = max(maxwidth, self.style.minwidth)
|
||||
self.laidout_height = sum(lineheights) + len(lineheights) * self.style.line_spacing
|
||||
self.laidout = "\n".join(lines)
|
||||
self.height = len(lines) * (font.get_linesize() + self.style.line_height_fudge)
|
||||
self.width = max(maxwidth, self.style.minwidth)
|
||||
|
||||
def render_pass(self, r, xo, yo, color, user_colors, length):
|
||||
"""
|
||||
Renders the text to r at xo, yo. Color is the base color,
|
||||
and user_colors controls if the user can override those colors.
|
||||
|
||||
Returns True if all characters were rendered, or False if a
|
||||
length restriction stopped some from being rendered.
|
||||
"""
|
||||
|
||||
y = yo
|
||||
indent = self.style.first_indent
|
||||
rest_indent = self.style.rest_indent
|
||||
antialias = self.style.antialias
|
||||
line_spacing = self.style.line_spacing
|
||||
|
||||
for line, line_height in zip(self.laidout, self.laidout_lineheights):
|
||||
x = xo + indent
|
||||
indent = rest_indent
|
||||
|
||||
max_ascent = 0
|
||||
|
||||
for ts, text in line:
|
||||
max_ascent = max(ts.get_ascent(), max_ascent)
|
||||
|
||||
for ts, text in line:
|
||||
|
||||
length -= len(text)
|
||||
if length < 0:
|
||||
text = text[:length]
|
||||
|
||||
surf = ts.render(text, antialias, color, user_colors)
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
r.blit(surf, (x, y + max_ascent - ts.get_ascent()))
|
||||
|
||||
x = x + sw
|
||||
|
||||
if length <= 0:
|
||||
return False
|
||||
|
||||
y = y + line_height + line_spacing
|
||||
|
||||
return True
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
|
||||
length = int(st * renpy.config.annoying_text_cps)
|
||||
else:
|
||||
length = sys.maxint
|
||||
self.slow = False
|
||||
|
||||
if self.style.drop_shadow:
|
||||
dsxo, dsyo = self.style.drop_shadow
|
||||
else:
|
||||
dsxo, dsyo = 0, 0
|
||||
|
||||
absxo = abs(dsxo)
|
||||
absyo = abs(dsyo)
|
||||
if not hasattr(self, "laidout"):
|
||||
self.layout(width - dsxo)
|
||||
|
||||
width -= absxo
|
||||
surf = renpy.display.render.Render(self.width + dsxo, self.height + dsyo)
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
if dsxo < 0:
|
||||
xo = -dsxo
|
||||
dsxo = 0
|
||||
laidout = self.laidout
|
||||
|
||||
# Annoying text hack.
|
||||
if self.slow and renpy.config.annoying_text_cps and not renpy.game.preferences.fast_text:
|
||||
chars = int(st * renpy.config.annoying_text_cps)
|
||||
if chars < len(laidout):
|
||||
laidout = laidout[:chars]
|
||||
renpy.display.render.redraw(self, 0)
|
||||
else:
|
||||
xo = 0
|
||||
self.slow = False
|
||||
else:
|
||||
self.slow = False
|
||||
|
||||
if dsyo < 0:
|
||||
yo = -dsyo
|
||||
dsyo = 0
|
||||
else:
|
||||
yo = 0
|
||||
|
||||
lines = laidout.split('\n')
|
||||
|
||||
first_indent = self.style.first_indent
|
||||
rest_indent = self.style.rest_indent
|
||||
|
||||
|
||||
# Common rendering code.
|
||||
def render_lines(x, y, color):
|
||||
|
||||
y += self.style.text_y_fudge
|
||||
|
||||
indent = first_indent
|
||||
|
||||
for l in lines:
|
||||
ls = font.render(l, self.style.antialias, color)
|
||||
|
||||
lw, lh = ls.get_size()
|
||||
lw += indent
|
||||
|
||||
xo = int((self.width - lw) * self.style.textalign)
|
||||
surf.blit(ls, (x + xo + indent, y + font.get_descent()))
|
||||
|
||||
y += font.get_linesize() + self.style.line_height_fudge
|
||||
indent = rest_indent
|
||||
|
||||
self.layout(width - absxo)
|
||||
|
||||
rv = renpy.display.render.Render(self.laidout_width + absxo, self.laidout_height + absyo)
|
||||
|
||||
|
||||
|
||||
# Render drop-shadow.
|
||||
if self.style.drop_shadow:
|
||||
self.render_pass(rv, dsxo, dsyo, self.style.drop_shadow_color, False, length)
|
||||
render_lines(dsxo, dsyo, self.style.drop_shadow_color)
|
||||
|
||||
self.slow = not self.render_pass(rv, xo, yo, self.style.color, True, length)
|
||||
# Render foreground.
|
||||
render_lines(0, 0, self.style.color)
|
||||
|
||||
if self.slow:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
return surf
|
||||
|
||||
def event(self, ev, x, y):
|
||||
"""
|
||||
@@ -477,7 +224,6 @@ class Text(renpy.display.core.Displayable):
|
||||
self.slow = False
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
|
||||
class ParameterizedText(object):
|
||||
"""
|
||||
This can be used as an image. When used, this image is expected to
|
||||
@@ -499,4 +245,3 @@ class ParameterizedText(object):
|
||||
|
||||
return Text(string, style=self.style, **self.properties)
|
||||
|
||||
|
||||
|
||||
+13
-48
@@ -60,7 +60,6 @@ class Transition(renpy.display.core.Displayable):
|
||||
"""
|
||||
|
||||
def __init__(self, delay):
|
||||
super(Transition, self).__init__()
|
||||
self.delay = delay
|
||||
self.offsets = [ ]
|
||||
self.events = True
|
||||
@@ -71,9 +70,6 @@ class Transition(renpy.display.core.Displayable):
|
||||
else:
|
||||
return None
|
||||
|
||||
def find_focusable(self, callback, focus_name):
|
||||
self.new_widget.find_focusable(callback, focus_name)
|
||||
|
||||
class Fade(Transition):
|
||||
"""
|
||||
This returns an object that can be used as an argument to a with
|
||||
@@ -138,7 +134,7 @@ class Fade(Transition):
|
||||
if widget:
|
||||
surf = render(widget, width, height, st)
|
||||
|
||||
rv.blit(surf, (0, 0), focus=events)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
self.events = events
|
||||
|
||||
@@ -234,58 +230,27 @@ class Dissolve(Transition):
|
||||
self.new_widget = new_widget
|
||||
self.events = False
|
||||
|
||||
self.old_bottom = None
|
||||
self.old_top = None
|
||||
self.old_alpha = 0
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if st >= self.time:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
if st < self.time:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
rv = render(self.old_widget, width, height, st)
|
||||
|
||||
alpha = min(255, int(255 * st / self.time))
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
bottom = render(self.old_widget, width, height, st)
|
||||
top = render(self.new_widget, width, height, st)
|
||||
|
||||
surf = top.pygame_surface(False)
|
||||
renpy.display.render.mutated_surface(surf)
|
||||
renpy.display.render.mutable_surface(surf)
|
||||
|
||||
rv.focuses.extend(top.focuses)
|
||||
|
||||
if id(top) == self.old_top and id(bottom) == self.old_bottom:
|
||||
|
||||
# Fast rendering path.
|
||||
|
||||
alpha = alpha / 255.0
|
||||
change = ( alpha - self.old_alpha) / ( 1.0 - self.old_alpha)
|
||||
change = int(change * 255.0)
|
||||
|
||||
surf.set_alpha(change, RLEACCEL)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
change /= 255.0
|
||||
self.old_alpha = self.old_alpha * ( 1 - change ) + change
|
||||
|
||||
else:
|
||||
|
||||
# Complete rendering path.
|
||||
|
||||
rv.blit(bottom, (0, 0), focus=False)
|
||||
surf.set_alpha(alpha, RLEACCEL)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
self.old_alpha = alpha / 255.0
|
||||
alpha = min(255, int(255 * st / self.time))
|
||||
|
||||
|
||||
self.old_top = id(top)
|
||||
self.old_bottom = id(bottom)
|
||||
surf.set_alpha(alpha, RLEACCEL)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
if st < self.time:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -358,7 +323,7 @@ class CropMove(Transition):
|
||||
image. Otherwise, the top layer contains the old image.
|
||||
"""
|
||||
|
||||
super(CropMove, self).__init__(time)
|
||||
|
||||
self.time = time
|
||||
|
||||
if mode == "wiperight":
|
||||
@@ -509,11 +474,11 @@ class CropMove(Transition):
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
rv.blit(render(self.bottom, width, height, st), (0, 0), focus=not self.topnew)
|
||||
rv.blit(render(self.bottom, width, height, st), (0, 0))
|
||||
|
||||
top = render(self.top, width, height, st)
|
||||
ss = top.subsurface(crop, focus=self.topnew)
|
||||
rv.blit(ss, pos, focus=self.topnew)
|
||||
ss = top.subsurface(crop)
|
||||
rv.blit(ss, pos)
|
||||
|
||||
renpy.display.render.redraw(self, 0)
|
||||
return rv
|
||||
|
||||
@@ -160,8 +160,9 @@ class Movie(renpy.display.layout.Null):
|
||||
def render(self, width, height, st):
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
|
||||
if surface:
|
||||
renpy.display.render.mutated_surface(surface)
|
||||
renpy.display.render.mutable_surface(surface)
|
||||
|
||||
w, h = surface.get_size()
|
||||
rv = renpy.display.render.Render(w, h)
|
||||
|
||||
+1
-7
@@ -22,8 +22,6 @@ class Context(object):
|
||||
@ivar rollback: True if this context participates in rollbacks.
|
||||
|
||||
@ivar runtime: The time spent in this context, in milliseconds.
|
||||
|
||||
@ivar info: A RevertableObject, which is made available to user code.
|
||||
"""
|
||||
|
||||
def __init__(self, rollback, context=None):
|
||||
@@ -32,15 +30,12 @@ class Context(object):
|
||||
self.return_stack = [ ]
|
||||
self.rollback = rollback
|
||||
self.runtime = 0
|
||||
self.info = renpy.python.RevertableObject()
|
||||
|
||||
|
||||
oldsl = None
|
||||
if context:
|
||||
oldsl = context.scene_lists
|
||||
self.runtime = context.runtime
|
||||
|
||||
vars(self.info).update(vars(context.info))
|
||||
|
||||
self.scene_lists = renpy.display.core.SceneLists(oldsl)
|
||||
|
||||
def goto_label(self, node_name):
|
||||
@@ -120,7 +115,6 @@ class Context(object):
|
||||
rv.current = self.current
|
||||
rv.scene_lists = self.scene_lists.rollback_copy()
|
||||
rv.runtime = self.runtime
|
||||
rv.info = self.info
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
+12
-61
@@ -6,16 +6,16 @@
|
||||
import renpy
|
||||
|
||||
# Many of these shouldn't be used directly.
|
||||
# from renpy.display.layout import *
|
||||
from renpy.display.text import ParameterizedText
|
||||
from renpy.display.behavior import Keymap
|
||||
# from renpy.display.image import *
|
||||
from renpy.display.layout import *
|
||||
from renpy.display.text import *
|
||||
from renpy.display.behavior import *
|
||||
from renpy.display.image import *
|
||||
|
||||
from renpy.curry import curry
|
||||
# from renpy.display.audio import music_start, music_stop
|
||||
from renpy.display.audio import music_start, music_stop
|
||||
from renpy.display.audio import play
|
||||
from renpy.display.video import movie_start_fullscreen, movie_start_displayable, movie_stop
|
||||
from renpy.loadsave import load, save, saved_games
|
||||
from renpy.loadsave import *
|
||||
from renpy.python import py_eval as eval
|
||||
from renpy.python import rng as random
|
||||
|
||||
@@ -112,7 +112,7 @@ def watch(expression, style='default', **properties):
|
||||
|
||||
renpy.config.overlay_functions.append(overlay_func)
|
||||
|
||||
def input(prompt, default='', allow=None, exclude='{}', length=None):
|
||||
def input(prompt, default='', length=None):
|
||||
"""
|
||||
This pops up a window requesting that the user enter in some text.
|
||||
It returns the entered text.
|
||||
@@ -123,19 +123,13 @@ def input(prompt, default='', allow=None, exclude='{}', length=None):
|
||||
|
||||
@param length: If given, a limit to the amount of text that this
|
||||
function will return.
|
||||
|
||||
@param allow: If not None, then if an input character is not in this
|
||||
string, it is ignored.
|
||||
|
||||
@param exclude: If not None, then if an input character is in this
|
||||
set, it is ignored.
|
||||
"""
|
||||
|
||||
renpy.ui.window(style='input_window')
|
||||
renpy.ui.vbox()
|
||||
|
||||
renpy.ui.text(prompt, style='input_prompt')
|
||||
renpy.ui.input(default, length=length, style='input_text', allow=allow, exclude=exclude)
|
||||
renpy.ui.input(default, length=length, style='input_text')
|
||||
|
||||
renpy.ui.close()
|
||||
|
||||
@@ -200,22 +194,6 @@ def display_menu(items, window_style='menu_window'):
|
||||
|
||||
return rv
|
||||
|
||||
class TagQuotingDict(object):
|
||||
def __getitem__(self, key):
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if key in store:
|
||||
rv = store[key]
|
||||
|
||||
if isinstance(rv, (str, unicode)):
|
||||
rv = rv.replace("{", "{{")
|
||||
|
||||
return rv
|
||||
else:
|
||||
raise Exception("During an interpolation, '%s' was not found as a variable." % key)
|
||||
|
||||
tag_quoting_dict = TagQuotingDict()
|
||||
|
||||
def say(who, what):
|
||||
"""
|
||||
@@ -225,13 +203,13 @@ def say(who, what):
|
||||
"""
|
||||
|
||||
# Interpolate variables.
|
||||
what = what % tag_quoting_dict
|
||||
what = what % renpy.game.store
|
||||
|
||||
if who is None:
|
||||
who = renpy.store.narrator
|
||||
|
||||
if isinstance(who, (str, unicode)):
|
||||
renpy.store.say(who, what)
|
||||
display_say(who, what, what_style='say_dialogue')
|
||||
else:
|
||||
who(what)
|
||||
|
||||
@@ -313,6 +291,8 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
|
||||
renpy.ui.imagemap(ground, selected, hotspots, unselected=unselected,
|
||||
style=style, **properties)
|
||||
|
||||
renpy.ui.keymousebehavior()
|
||||
|
||||
rv = renpy.ui.interact(suppress_overlay=(not overlays))
|
||||
checkpoint()
|
||||
return rv
|
||||
@@ -478,14 +458,6 @@ def windows():
|
||||
import sys
|
||||
return hasattr(sys, 'winver')
|
||||
|
||||
def version():
|
||||
"""
|
||||
Returns a string containing the current version of Ren'Py, prefixed with the
|
||||
string "Ren\'Py ".
|
||||
"""
|
||||
|
||||
return renpy.version
|
||||
|
||||
def transition(trans, layer=None):
|
||||
"""
|
||||
Sets the transition that will be used for the next
|
||||
@@ -525,19 +497,6 @@ def get_game_runtime():
|
||||
|
||||
return renpy.game.context().runtime / 1000.0
|
||||
|
||||
def loadable(filename):
|
||||
"""
|
||||
Returns True if the given filename is loadable, meaning that it
|
||||
can be loaded from the disk or from inside an archive. Returns
|
||||
False if this is not the case.
|
||||
"""
|
||||
|
||||
try:
|
||||
renpy.loader.load(filename)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def exists(filename):
|
||||
"""
|
||||
Returns true if the given filename can be found in the
|
||||
@@ -563,14 +522,6 @@ def restart_interaction():
|
||||
|
||||
renpy.game.interface.restart_interaction = True
|
||||
|
||||
def context():
|
||||
"""
|
||||
Returns an object that is unique to the current context, that
|
||||
participates in rollback and the like.
|
||||
"""
|
||||
|
||||
return renpy.game.context().info
|
||||
|
||||
|
||||
call_in_new_context = renpy.game.call_in_new_context
|
||||
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
|
||||
|
||||
@@ -21,6 +21,11 @@ searchpath = [ ]
|
||||
# A Script object, giving the script of the currently executing game.
|
||||
script = None
|
||||
|
||||
# The store is where Ren'Py python results are stored. We first need
|
||||
# to import in the module, and then we use the module's dictionary
|
||||
# directly.
|
||||
store = None
|
||||
|
||||
# A shallow copy of the store made at the end of the init phase. If
|
||||
# a key in here points to the same value here as it does in the store,
|
||||
# it is not saved.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import renpy
|
||||
import os.path
|
||||
from pickle import loads
|
||||
from cPickle import loads
|
||||
from cStringIO import StringIO
|
||||
|
||||
archives = [ ]
|
||||
|
||||
+10
-23
@@ -1,6 +1,6 @@
|
||||
# This file contains functions that load and save the game state.
|
||||
|
||||
from pickle import dumps, loads, HIGHEST_PROTOCOL
|
||||
from cPickle import dumps, loads, HIGHEST_PROTOCOL
|
||||
import cStringIO
|
||||
import zipfile
|
||||
import time
|
||||
@@ -15,12 +15,8 @@ savegame_suffix = renpy.savegame_suffix
|
||||
|
||||
def debug_dump(prefix, o, seen):
|
||||
|
||||
if isinstance(o, (int, str, float, bool)):
|
||||
print prefix, o
|
||||
return
|
||||
|
||||
if id(o) in seen:
|
||||
print prefix, "@%x" % id(o)
|
||||
print prefix, id(o)
|
||||
return
|
||||
|
||||
seen[id(o)] = True
|
||||
@@ -135,25 +131,16 @@ def saved_games():
|
||||
|
||||
for f in files:
|
||||
|
||||
try:
|
||||
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
|
||||
extra_info = zf.read("extra_info")
|
||||
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
|
||||
zf.close()
|
||||
|
||||
screenshot = renpy.display.image.UncachedImage(sio, "screenshot.tga", False)
|
||||
|
||||
zf = zipfile.ZipFile(renpy.config.savedir + "/" + f, "r")
|
||||
extra_info = zf.read("extra_info")
|
||||
sio = cStringIO.StringIO(zf.read("screenshot.tga"))
|
||||
zf.close()
|
||||
f = f[:-len(savegame_suffix)]
|
||||
|
||||
screenshot = renpy.display.image.UncachedImage(sio, "screenshot.tga", False)
|
||||
|
||||
f = f[:-len(savegame_suffix)]
|
||||
|
||||
saveinfo[f] = screenshot, extra_info
|
||||
|
||||
except:
|
||||
if renpy.config.debug:
|
||||
raise Exception
|
||||
|
||||
if newest not in saveinfo:
|
||||
newest = None
|
||||
saveinfo[f] = screenshot, extra_info
|
||||
|
||||
return saveinfo, newest
|
||||
|
||||
|
||||
+4
-9
@@ -12,7 +12,7 @@
|
||||
import renpy
|
||||
import renpy.game as game
|
||||
import os
|
||||
from pickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
from cPickle import loads, dumps, HIGHEST_PROTOCOL
|
||||
|
||||
def run(restart=False):
|
||||
"""
|
||||
@@ -54,11 +54,6 @@ def run(restart=False):
|
||||
game.persistent._seen_ever = { }
|
||||
|
||||
game.seen_ever = game.persistent._seen_ever
|
||||
|
||||
# Initialize the set of images seen ever.
|
||||
if not game.persistent._seen_images:
|
||||
game.persistent._seen_images = { }
|
||||
|
||||
|
||||
# Clear the list of seen statements in this game.
|
||||
game.seen_session = { }
|
||||
@@ -71,11 +66,11 @@ def run(restart=False):
|
||||
|
||||
# Initialize the store.
|
||||
renpy.store.store = renpy.store
|
||||
game.store = vars(renpy.store)
|
||||
renpy.store.persistent = game.persistent
|
||||
renpy.store._preferences = game.preferences
|
||||
|
||||
# Set up styles.
|
||||
renpy.style.reset()
|
||||
game.style = renpy.style.StyleManager()
|
||||
renpy.store.style = game.style
|
||||
|
||||
@@ -93,14 +88,14 @@ def run(restart=False):
|
||||
renpy.game.exception_info = 'After initialization, but before game start.'
|
||||
|
||||
# Rebuild the various style caches.
|
||||
renpy.style.build_styles()
|
||||
game.style._build_style_caches()
|
||||
|
||||
# Index the archive files. We should not have loaded an image
|
||||
# before this point. (As pygame will not have been initialized.)
|
||||
renpy.loader.index_archives()
|
||||
|
||||
# Make a clean copy of the store.
|
||||
game.clean_store = vars(renpy.store).copy()
|
||||
game.clean_store = game.store.copy()
|
||||
|
||||
# Re-Initialize the log.
|
||||
game.log = renpy.python.RollbackLog()
|
||||
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
# This module contains code that handles the playing of music.
|
||||
|
||||
import pygame
|
||||
import renpy
|
||||
import sys # to detect windows.
|
||||
|
||||
# The Windows Volume Management Strategy (tm).
|
||||
|
||||
# We keep a master music volume, which is the volume we use directly
|
||||
# when playing music as mp3, ogg, etc. When we start up, we compute
|
||||
# a midi music scaling factor. This midi music scaling factor is
|
||||
# computed from the current master music volume such that when we are not
|
||||
# fading, if we pygame.mixer.music.set_volume() to the mmv * mmsv, we get
|
||||
# read the same value from midiOutGetVolume() as we did before we tried
|
||||
# doing that.
|
||||
|
||||
|
||||
playing_midi = True
|
||||
fading = False
|
||||
master_music_volume = 1.0
|
||||
|
||||
|
||||
windows_magic = False
|
||||
|
||||
if hasattr(sys, 'winver'):
|
||||
|
||||
midi_msf = 0.0
|
||||
|
||||
last_raw_volume = -1
|
||||
|
||||
def read_raw_volume():
|
||||
res = c_uint()
|
||||
|
||||
for i in range(0, winmm.midiOutGetNumDevs()):
|
||||
rv = winmm.midiOutGetVolume(i, byref(res))
|
||||
|
||||
if not rv:
|
||||
return res.value
|
||||
else:
|
||||
print "Couldn't read raw midi volume."
|
||||
return -1
|
||||
|
||||
|
||||
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
|
||||
|
||||
def compute_midi_msf():
|
||||
"""
|
||||
Computes the Midi MSF. Returns True if successful, False if otherwise.
|
||||
"""
|
||||
|
||||
# Don't update the MSF when fading is going on, or when not
|
||||
# playing a midi. (Except before playing any music whatsoever.)
|
||||
if fading or not playing_midi:
|
||||
return False
|
||||
|
||||
global last_raw_volume
|
||||
|
||||
raw_vol = read_raw_volume()
|
||||
|
||||
if raw_vol < 0:
|
||||
return False
|
||||
|
||||
# The case in which the volume hasn't changed recently.
|
||||
if raw_vol == last_raw_volume:
|
||||
return True
|
||||
|
||||
last_raw_volume = raw_vol
|
||||
|
||||
# print "raw_vol", raw_vol
|
||||
|
||||
# The fraction that the midi mixer is at.
|
||||
mixfrac = 1.0 * ( raw_vol & 0xffff ) / 0xffff
|
||||
|
||||
global midi_msf
|
||||
midi_msf = mixfrac / master_music_volume
|
||||
|
||||
# print "Midi msf is now:", midi_msf
|
||||
|
||||
return True
|
||||
|
||||
# This should get called after the music starts playing.
|
||||
def set_music_volume(vol):
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
if playing_midi:
|
||||
|
||||
vol *= midi_msf
|
||||
if vol > 1.0:
|
||||
vol = 1.0
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
global last_raw_volume
|
||||
last_raw_volume = read_raw_volume()
|
||||
|
||||
# Figure out the default msf, and set it up.
|
||||
windows_magic = compute_midi_msf()
|
||||
playing_midi = False
|
||||
|
||||
except Exception, e:
|
||||
print "Exception when trying to init music:", str(e)
|
||||
print "Falling back to Unix mode."
|
||||
|
||||
if not windows_magic:
|
||||
|
||||
def compute_midi_msf():
|
||||
return
|
||||
|
||||
def set_music_volume(vol):
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
|
||||
playing_midi = False
|
||||
|
||||
# This detects if the filename is a midi, and sets playing_midi
|
||||
# appropriately.
|
||||
def detect_midi(fn):
|
||||
|
||||
fn = fn.lower()
|
||||
|
||||
global playing_midi
|
||||
playing_midi = fn.endswith(".mid") or fn.endswith(".midi")
|
||||
|
||||
|
||||
# Information about the currently playing track.
|
||||
current_music = None
|
||||
|
||||
def music_delay(offset):
|
||||
"""
|
||||
Returns the time left until the current music has been playing for
|
||||
offset seconds. If music is not playing, return None. May return
|
||||
a negative time.
|
||||
"""
|
||||
|
||||
mo = pygame.mixer.music.get_pos()
|
||||
if mo < 0:
|
||||
return None
|
||||
|
||||
mo /= 1000.0
|
||||
|
||||
return offset - mo
|
||||
|
||||
|
||||
|
||||
def music_start(filename, loops=-1, startpos=0.0):
|
||||
"""
|
||||
This starts music playing. If a music track is already playing,
|
||||
stops that track in favor of this one.
|
||||
|
||||
@param filename: The file that the music will be played from. This
|
||||
is relative to the game directory, and must be a real file (so it
|
||||
cannot be stored in an archive.)
|
||||
|
||||
@param loops: The number of times the music will loop after it
|
||||
finishes playing. If negative, the music will loop indefinitely.
|
||||
Please note that even once the song has finished, rollback or load
|
||||
may cause it to start playing again. So it may not be safe to have
|
||||
this set to a non-negative value.
|
||||
|
||||
@param startpos: The number of seconds into the music to start playing.
|
||||
"""
|
||||
|
||||
music_stop()
|
||||
renpy.game.context().scene_lists.music = (filename, loops, startpos)
|
||||
restore()
|
||||
|
||||
|
||||
def music_stop():
|
||||
"""
|
||||
Stops the currently playing music track.
|
||||
"""
|
||||
|
||||
renpy.game.context().scene_lists.music = None
|
||||
restore()
|
||||
|
||||
def restore():
|
||||
"""
|
||||
This makes sure that the current music matches the music found in
|
||||
the context.
|
||||
"""
|
||||
|
||||
global current_music
|
||||
global fading
|
||||
|
||||
compute_midi_msf()
|
||||
set_music_volume(1.0)
|
||||
|
||||
new_music = renpy.game.context().scene_lists.music
|
||||
|
||||
if not renpy.game.preferences.music:
|
||||
new_music = None
|
||||
|
||||
if current_music == new_music:
|
||||
return
|
||||
|
||||
# Usually, ignore errors.
|
||||
try:
|
||||
if current_music != new_music and current_music:
|
||||
current_music = None
|
||||
pygame.mixer.music.fadeout(int(renpy.config.fade_music * 1000))
|
||||
fading = True
|
||||
else:
|
||||
if not pygame.mixer.music.get_busy():
|
||||
fn, loops, startpos = new_music
|
||||
|
||||
fading = False
|
||||
detect_midi(fn)
|
||||
|
||||
pygame.mixer.music.load(renpy.game.basepath + "/" + fn)
|
||||
pygame.mixer.music.play(loops, startpos)
|
||||
|
||||
set_music_volume(master_music_volume)
|
||||
|
||||
current_music = new_music
|
||||
|
||||
except pygame.error, e:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
print "Error while trying to play music:", str(e)
|
||||
+7
-13
@@ -5,7 +5,6 @@ import codecs
|
||||
import re
|
||||
import os
|
||||
|
||||
import renpy
|
||||
import renpy.ast as ast
|
||||
|
||||
class ParseError(Exception):
|
||||
@@ -143,7 +142,7 @@ def list_logical_lines(filename):
|
||||
|
||||
|
||||
if line != "":
|
||||
raise ParseError(filename, start_number, "is not terminated with a newline (check quotes and parenthesis).")
|
||||
raise ParseError(filename, number, "is not terminated with a newline.")
|
||||
|
||||
return rv
|
||||
|
||||
@@ -172,11 +171,13 @@ def group_logical_lines(lines):
|
||||
|
||||
if l[index] == '\t':
|
||||
index += 1
|
||||
depth = depth + 8 - (depth % 8)
|
||||
depth = depth + 8 - (16 % 8)
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
# TODO: Fix to handle tabs properly. Or else update the docs to
|
||||
# forbid tabs entirely.
|
||||
return depth, l[depth:]
|
||||
|
||||
# i, min_depth -> block, new_i
|
||||
@@ -465,17 +466,12 @@ class Lexer(object):
|
||||
if c == 'u':
|
||||
self.pos += 1
|
||||
|
||||
if self.pos == len(self.text):
|
||||
self.pos -= 1
|
||||
if self.eol():
|
||||
return False
|
||||
|
||||
c = self.text[self.pos]
|
||||
|
||||
if c not in ('"', "'"):
|
||||
self.pos -= 1
|
||||
return False
|
||||
|
||||
elif c not in ('"', "'"):
|
||||
if c not in ('"', "'"):
|
||||
return False
|
||||
|
||||
delim = c
|
||||
@@ -517,7 +513,7 @@ class Lexer(object):
|
||||
while self.match(r'\.'):
|
||||
n = self.name()
|
||||
if not n:
|
||||
self.error('expecting name.')
|
||||
self.parse_error('expecting name.')
|
||||
|
||||
rv += "." + n
|
||||
|
||||
@@ -1188,8 +1184,6 @@ def parse(fn):
|
||||
statements that were found at the top level of the file.
|
||||
"""
|
||||
|
||||
renpy.game.exception_info = 'While parsing ' + fn + '.'
|
||||
|
||||
version = os.stat(fn).st_mtime
|
||||
lines = list_logical_lines(fn)
|
||||
nested = group_logical_lines(lines)
|
||||
|
||||
+19
-28
@@ -62,7 +62,7 @@ def reached(obj, path, reachable):
|
||||
def reached_vars(store, reachable):
|
||||
"""
|
||||
Marks everything reachable from the variables in the store
|
||||
or from the context info objects as reachable.
|
||||
as reachable.
|
||||
|
||||
@param store: A map from variable name to variable value.
|
||||
@param reachable: A dictionary mapping reached object ids to
|
||||
@@ -72,9 +72,7 @@ def reached_vars(store, reachable):
|
||||
for k, v in store.iteritems():
|
||||
reached(v, k, reachable)
|
||||
|
||||
for c in renpy.game.contexts:
|
||||
reached(c.info, "#context", reachable)
|
||||
|
||||
|
||||
|
||||
##### Code that replaces literals will calls to magic constructors.
|
||||
|
||||
@@ -358,10 +356,10 @@ class Rollback(renpy.object.Object):
|
||||
for t in self.store:
|
||||
if len(t) == 2:
|
||||
k, v = t
|
||||
vars(renpy.store)[k] = v
|
||||
renpy.game.store[k] = v
|
||||
else:
|
||||
k, = t
|
||||
del vars(renpy.store)[k]
|
||||
del renpy.game.store[k]
|
||||
|
||||
renpy.game.contexts = [ self.context ]
|
||||
rng.pushback(self.random)
|
||||
@@ -430,7 +428,7 @@ class RollbackLog(renpy.object.Object):
|
||||
self.log.append(self.current)
|
||||
|
||||
self.mutated = { }
|
||||
self.old_store = vars(renpy.store).copy()
|
||||
self.old_store = renpy.game.store.copy()
|
||||
|
||||
def complete(self):
|
||||
"""
|
||||
@@ -441,7 +439,7 @@ class RollbackLog(renpy.object.Object):
|
||||
occurs.
|
||||
"""
|
||||
|
||||
new_store = vars(renpy.store)
|
||||
new_store = renpy.game.store
|
||||
store = [ ]
|
||||
|
||||
|
||||
@@ -486,11 +484,9 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
rv = { }
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
for k in self.ever_been_changed.keys():
|
||||
if k in store:
|
||||
rv[k] = store[k]
|
||||
if k in renpy.game.store:
|
||||
rv[k] = renpy.game.store[k]
|
||||
|
||||
return rv
|
||||
|
||||
@@ -621,15 +617,14 @@ class RollbackLog(renpy.object.Object):
|
||||
renpy.game.log = self
|
||||
|
||||
# Restore the store.
|
||||
store = vars(renpy.store)
|
||||
store.clear()
|
||||
store.update(renpy.game.clean_store)
|
||||
renpy.game.store.clear()
|
||||
renpy.game.store.update(renpy.game.clean_store)
|
||||
|
||||
for k in self.ever_been_changed:
|
||||
if k in store:
|
||||
del store[k]
|
||||
if k in renpy.game.store:
|
||||
del renpy.game.store[k]
|
||||
|
||||
store.update(self.frozen_roots)
|
||||
renpy.game.store.update(self.frozen_roots)
|
||||
self.frozen_roots = None
|
||||
|
||||
# Now, rollback to an acceptable point.
|
||||
@@ -639,35 +634,31 @@ class RollbackLog(renpy.object.Object):
|
||||
|
||||
def py_exec_bytecode(bytecode, hide=False):
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if hide:
|
||||
locals = { }
|
||||
else:
|
||||
locals = store
|
||||
locals = renpy.game.store
|
||||
|
||||
exec marshal.loads(bytecode) in store, locals
|
||||
exec marshal.loads(bytecode) in renpy.game.store, locals
|
||||
|
||||
|
||||
def py_exec(source, hide=False):
|
||||
|
||||
store = vars(renpy.store)
|
||||
|
||||
if hide:
|
||||
locals = { }
|
||||
else:
|
||||
locals = store
|
||||
locals = renpy.game.store
|
||||
|
||||
|
||||
exec py_compile(source, 'exec') in store, locals
|
||||
exec py_compile(source, 'exec') in renpy.game.store, locals
|
||||
|
||||
def py_eval_bytecode(bytecode):
|
||||
|
||||
return eval(marshal.loads(bytecode), vars(renpy.store))
|
||||
return eval(marshal.loads(bytecode), renpy.game.store)
|
||||
|
||||
def py_eval(source):
|
||||
source = source.strip()
|
||||
|
||||
return eval(py_compile(source, 'eval'),
|
||||
vars(renpy.store))
|
||||
renpy.game.store)
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ import renpy
|
||||
import os.path
|
||||
import os
|
||||
|
||||
from pickle import loads, dumps
|
||||
from cPickle import loads, dumps
|
||||
|
||||
# The version of the dumped script.
|
||||
script_version = renpy.script_version
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Plays sounds.
|
||||
|
||||
import renpy
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
def init():
|
||||
try:
|
||||
bufsize = 4096
|
||||
|
||||
import os
|
||||
|
||||
if 'RENPY_SOUND_BUFSIZE' in os.environ:
|
||||
bufsize = int(os.environ('RENPY_SOUND_BUFSIZE'))
|
||||
|
||||
pygame.mixer.pre_init(renpy.config.sound_sample_rate, -16, 2, bufsize)
|
||||
except:
|
||||
try:
|
||||
pygame.mixer.pre_init()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
def play(fn, loops=0):
|
||||
"""
|
||||
This plays the given sound. The sound must be in a wav file,
|
||||
and expected to have a sample rate 44100hz (changable with
|
||||
config.sound_sample_rate), 16 bit, stereo. These expectations may
|
||||
be violated, but that may lead to conversion delays.
|
||||
|
||||
Once a sound has been started, there's no way to stop it.
|
||||
|
||||
@param fn: The name of the file that the sound is read from. This
|
||||
file may be contained in a game directory or an archive.
|
||||
|
||||
@param loops: The number of extra times the sound will be
|
||||
played. (The default, 0, will play the sound once.)
|
||||
"""
|
||||
|
||||
if not fn:
|
||||
return
|
||||
|
||||
if not renpy.game.preferences.sound:
|
||||
return
|
||||
|
||||
try:
|
||||
sound = pygame.mixer.Sound(renpy.loader.load(fn))
|
||||
sound.play()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
+39
-10
@@ -6,8 +6,6 @@
|
||||
import renpy
|
||||
|
||||
import renpy.ui as ui
|
||||
import renpy.display.im as im
|
||||
import renpy.display.audio as audio
|
||||
|
||||
from renpy.python import RevertableList as __renpy__list__
|
||||
list = __renpy__list__
|
||||
@@ -150,22 +148,53 @@ class DynamicCharacter(object):
|
||||
interact=interact,
|
||||
**self.properties)
|
||||
|
||||
# The color function. (Moved, since text needs it, too.)
|
||||
color = renpy.display.text.color
|
||||
|
||||
# Conveniently get rid of all the packages we had imported before.
|
||||
import renpy.exports as renpy
|
||||
|
||||
# The default narrator.
|
||||
def narrator(what, interact=True):
|
||||
renpy.display_say(None, what, what_style='say_thought', interact=interact)
|
||||
|
||||
# The default menu function.
|
||||
menu = renpy.display_menu
|
||||
|
||||
# The function that is called when anonymous text is said.
|
||||
def say(who, what):
|
||||
renpy.display_say(who, what)
|
||||
def color(s):
|
||||
"""
|
||||
This function converts a hexcode into a color/alpha tuple. Leading
|
||||
# marks are ignored. Colors can be rgb or rgba, with each element having
|
||||
either one or two digits. (So the strings can be 3, 4, 6, or 8 digits long,
|
||||
not including the optional #.) A missing alpha is interpreted as 255,
|
||||
fully opaque.
|
||||
|
||||
For example, color('#123a') returns (17, 34, 51, 170), while
|
||||
color('c0c0c0') returns (192, 192, 192, 255).
|
||||
"""
|
||||
|
||||
if s[0] == '#':
|
||||
s = s[1:]
|
||||
|
||||
if len(s) == 6:
|
||||
r = int(s[0]+s[1], 16)
|
||||
g = int(s[2]+s[3], 16)
|
||||
b = int(s[4]+s[5], 16)
|
||||
a = 255
|
||||
elif len(s) == 8:
|
||||
r = int(s[0]+s[1], 16)
|
||||
g = int(s[2]+s[3], 16)
|
||||
b = int(s[4]+s[5], 16)
|
||||
a = int(s[6]+s[7], 16)
|
||||
elif len(s) == 3:
|
||||
r = int(s[0], 16) * 0x11
|
||||
g = int(s[1], 16) * 0x11
|
||||
b = int(s[2], 16) * 0x11
|
||||
a = 255
|
||||
elif len(s) == 4:
|
||||
r = int(s[0], 16) * 0x11
|
||||
g = int(s[1], 16) * 0x11
|
||||
b = int(s[2], 16) * 0x11
|
||||
a = int(s[3], 16) * 0x11
|
||||
else:
|
||||
raise Exception("Argument to color() must be 3, 4, 6, or 8 hex digits long.")
|
||||
|
||||
return (r, g, b, a)
|
||||
|
||||
# The default transition.
|
||||
default_transition = None
|
||||
|
||||
+121
-155
@@ -1,192 +1,158 @@
|
||||
import renpy
|
||||
|
||||
# A list of style prefixes we care about, including no prefix.
|
||||
prefixes = [ 'hover_', 'idle_', 'activate_' , 'insensitive_' ]
|
||||
|
||||
def startswith_prefix(s):
|
||||
for i in prefixes:
|
||||
if s.startswith(i):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
prefixes = [ 'hover_', 'idle_', 'activate_', '' ]
|
||||
|
||||
substitutes = dict(
|
||||
xmargin = [ 'left_margin', 'right_margin' ],
|
||||
ymargin = [ 'top_margin', 'bottom_margin' ],
|
||||
|
||||
xpadding = [ 'left_padding', 'right_padding' ],
|
||||
ypadding = [ 'top_padding', 'bottom_padding' ],
|
||||
)
|
||||
|
||||
# Expand out substitutes:
|
||||
for k in substitutes.keys():
|
||||
for p in prefixes:
|
||||
substitutes[p + k] = [ p + i for i in substitutes[k] ]
|
||||
|
||||
# A map from a style name to the style associated with that name.
|
||||
style_map = { }
|
||||
|
||||
# True if we have expanded all of the style caches, False otherwise.
|
||||
styles_built = False
|
||||
|
||||
# A list of styles that are pending expansion.
|
||||
styles_pending = [ ]
|
||||
|
||||
# A list of created styles giving the style's name, parent, and description.
|
||||
style_info = [ ]
|
||||
|
||||
def reset():
|
||||
"""
|
||||
This resets all of the data structures associated with style
|
||||
management.
|
||||
"""
|
||||
|
||||
global style_map
|
||||
global styles_built
|
||||
global styles_pending
|
||||
global style_info
|
||||
|
||||
style_map = { }
|
||||
styles_built = False
|
||||
styles_pending = [ ]
|
||||
style_info = [ ]
|
||||
|
||||
|
||||
class StyleManager(object):
|
||||
"""
|
||||
This is the singleton object that is exported into the store
|
||||
as style
|
||||
This is the singleton object that is exported into the store as
|
||||
'style', and to everyone as renpy.game.style. It's responsible for
|
||||
mapping style names to styles.
|
||||
"""
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return style_map[name]
|
||||
except:
|
||||
raise Exception('The style %s does not exist.' % name)
|
||||
def __init__(self):
|
||||
self._style_list = [ ]
|
||||
|
||||
def create(self, name, parent, description=None):
|
||||
style_map[name] = Style(parent, { })
|
||||
def create(self, name, parent='default', description=''):
|
||||
"""
|
||||
Creates a new style with the given parent and description, and
|
||||
adds it to the StyleManager.
|
||||
"""
|
||||
|
||||
if description:
|
||||
style_info.append((name, parent, description))
|
||||
|
||||
# This expands out property names and adds them to style's property
|
||||
# dictionary.
|
||||
def compute_properties(style, properties):
|
||||
|
||||
props = { }
|
||||
|
||||
# Expand substitutions.
|
||||
for k, v in properties.items():
|
||||
if k in substitutes:
|
||||
for j in substitutes[k]:
|
||||
props[j] = v
|
||||
else:
|
||||
props[k] = v
|
||||
|
||||
# Expand prefixes, where necessary.
|
||||
for k, v in props.items():
|
||||
if startswith_prefix(k):
|
||||
continue
|
||||
|
||||
del props[k]
|
||||
|
||||
for p in prefixes:
|
||||
props[p + k] = v
|
||||
|
||||
style.properties.update(props)
|
||||
style.cache.update(props)
|
||||
|
||||
# This builds the style. If recurse is True, this also builds the
|
||||
# parent style.
|
||||
def build_style(style, recurse=False):
|
||||
|
||||
style.cache.clear()
|
||||
|
||||
if style.parent:
|
||||
|
||||
try:
|
||||
parent = style_map[style.parent]
|
||||
except:
|
||||
raise Exception('Style %s is not known.' % style.parent)
|
||||
|
||||
if recurse:
|
||||
build_style(parent)
|
||||
if parent and not hasattr(self, parent):
|
||||
raise Exception("Style '%s' has non-existent parent '%s'." % (name, parent))
|
||||
|
||||
style.cache.update(parent.cache)
|
||||
s = Style(parent, defer=True)
|
||||
s.name = name
|
||||
s.parent = parent
|
||||
s.description = description
|
||||
|
||||
style.cache.update(style.properties)
|
||||
setattr(self, name, s)
|
||||
|
||||
# This builds all pending styles, recursing to ensure that they are built
|
||||
# in the right order.
|
||||
def build_styles():
|
||||
self._style_list.append(s)
|
||||
|
||||
global styles_pending
|
||||
global styles_built
|
||||
def _build_style_caches(self):
|
||||
|
||||
for s in styles_pending:
|
||||
build_style(s, True)
|
||||
for i in self._style_list:
|
||||
i.build_cache()
|
||||
|
||||
styles_pending = None
|
||||
styles_built = True
|
||||
|
||||
def _write_docs(self, filename):
|
||||
|
||||
f = file(filename, "w")
|
||||
|
||||
import re
|
||||
|
||||
for s in self._style_list:
|
||||
f.write(' <renpy_style name="%s">' % s.name)
|
||||
|
||||
if s.parent:
|
||||
f.write('<renpy_style_inherits>%s</renpy_style_inherits>' % s.parent)
|
||||
|
||||
f.write(re.sub(r'\s+', ' ', s.description))
|
||||
f.write("</renpy_style>\n\n")
|
||||
|
||||
f.close()
|
||||
|
||||
class Style(object):
|
||||
"""
|
||||
This is an individual style object, which can have properties
|
||||
looked up on it or its parent. Call the constructor of this
|
||||
to create an anonymous style.
|
||||
"""
|
||||
|
||||
def __getstate__(self):
|
||||
return dict(prefix = self.prefix,
|
||||
parent = self.parent,
|
||||
cache = { },
|
||||
properties = self.properties)
|
||||
|
||||
return dict(properties=self.properties,
|
||||
prefix=self.prefix,
|
||||
parent=self.parent)
|
||||
|
||||
|
||||
def __setstate__(self, state):
|
||||
vars(self).update(state)
|
||||
build_style(self)
|
||||
self.__dict__.update(state)
|
||||
|
||||
def __init__(self, parent, properties):
|
||||
# This should always work, as only one layer of these styles will
|
||||
# be serialized.
|
||||
|
||||
fields = dict(
|
||||
prefix = 'insensitive_',
|
||||
parent = parent,
|
||||
cache = { },
|
||||
properties = { },
|
||||
)
|
||||
self.build_cache()
|
||||
|
||||
vars(self).update(fields)
|
||||
def __setattr__(self, key, value):
|
||||
|
||||
if key in substitutes:
|
||||
for i in substitutes[key]:
|
||||
self.__setattr__(i, value)
|
||||
|
||||
|
||||
return
|
||||
|
||||
for prefix in prefixes:
|
||||
prefkey = prefix + key
|
||||
|
||||
self.properties[prefkey] = value
|
||||
self.cache[prefkey] = value
|
||||
|
||||
def __getattr__(self, key):
|
||||
|
||||
cache = self.cache
|
||||
|
||||
try:
|
||||
return cache[self.prefix + key]
|
||||
except KeyError:
|
||||
raise AttributeError("Style property '%s' not found." % key)
|
||||
|
||||
def __delattr__(self, key):
|
||||
del self.properties[key]
|
||||
del self.cache[key]
|
||||
|
||||
def lookup(self, key, prefix):
|
||||
|
||||
cache = self.cache
|
||||
|
||||
try:
|
||||
return cache.get(prefix + key, cache[key])
|
||||
except KeyError:
|
||||
raise AttributeError("Style property '%s' not found." % key)
|
||||
|
||||
if styles_built:
|
||||
build_style(self)
|
||||
else:
|
||||
styles_pending.append(self)
|
||||
|
||||
compute_properties(self, properties)
|
||||
|
||||
def set_prefix(self, prefix):
|
||||
vars(self)["prefix"] = prefix
|
||||
self.prefix = prefix
|
||||
|
||||
|
||||
def build_cache(self):
|
||||
vars(self)["cache"] = { }
|
||||
|
||||
self.cache = { }
|
||||
|
||||
if self.parent:
|
||||
self.cache.update(getattr(renpy.game.style, self.parent).cache)
|
||||
|
||||
self.cache.update(self.properties)
|
||||
|
||||
def __init__(self, parent, properties=None, defer=False):
|
||||
|
||||
if parent and not hasattr(renpy.game.style, parent):
|
||||
raise Exception("Style '%s' is not known." % parent)
|
||||
|
||||
if not properties:
|
||||
properties = { }
|
||||
|
||||
for k in properties.keys():
|
||||
for p in prefixes:
|
||||
if p + k not in properties:
|
||||
properties[p + k] = properties[k]
|
||||
|
||||
vars(self)["parent"] = parent
|
||||
vars(self)["prefix"] = ''
|
||||
vars(self)["properties"] = properties
|
||||
vars(self)["cache"] = { }
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self.cache[self.prefix + name]
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
compute_properties(self, { name : value } )
|
||||
|
||||
|
||||
|
||||
def write_docs(filename):
|
||||
|
||||
f = file(filename, "w")
|
||||
|
||||
import re
|
||||
|
||||
for name, parent, description in style_info:
|
||||
f.write(' <renpy_style name="%s">' % name)
|
||||
|
||||
if parent:
|
||||
f.write('<renpy_style_inherits>%s</renpy_style_inherits>' % parent)
|
||||
|
||||
f.write(re.sub(r'\s+', ' ', description))
|
||||
f.write("</renpy_style>\n\n")
|
||||
|
||||
f.close()
|
||||
|
||||
|
||||
|
||||
if not defer:
|
||||
self.build_cache()
|
||||
|
||||
+12
-56
@@ -169,7 +169,7 @@ def vbox(padding=0, **properties):
|
||||
def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
|
||||
"""
|
||||
This creates a layout that places widgets in an evenly spaced
|
||||
grid. New widgets are added to this grid unil ui.close() is called.
|
||||
grid. New widges are added to this vbox unil ui.close() is called.
|
||||
Widgets are added by going from left to right within a single row,
|
||||
and down to the start of the next row when a row is full. All cells
|
||||
must be filled (that is, exactly col * rows widgets must be added to
|
||||
@@ -194,6 +194,8 @@ def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
|
||||
|
||||
return add(renpy.display.layout.Grid(cols, rows, padding, xfill=xfill, yfill=yfill, **properties), True)
|
||||
|
||||
|
||||
|
||||
def fixed(**properties):
|
||||
"""
|
||||
This creates a layout that places widgets at fixed locations
|
||||
@@ -242,12 +244,9 @@ def keymousebehavior():
|
||||
screen. The keymouse behavior allows the mouse to be controlled
|
||||
by the keyboard. This widget should not be added to any other
|
||||
widget, but should instead be only added to the screen itself.
|
||||
|
||||
As of 4.8, this does nothing, but is retained for compatability.
|
||||
"""
|
||||
|
||||
return
|
||||
|
||||
return add(renpy.display.behavior.KeymouseBehavior())
|
||||
|
||||
def saybehavior():
|
||||
"""
|
||||
@@ -281,12 +280,7 @@ def pausebehavior(delay, result=False):
|
||||
|
||||
return add(renpy.display.behavior.PauseBehavior(delay, result))
|
||||
|
||||
def menu(menuitems,
|
||||
style = 'menu',
|
||||
caption_style='menu_caption',
|
||||
choice_style='menu_choice',
|
||||
choice_button_style='menu_choice_button',
|
||||
**properties):
|
||||
def menu(menuitems, **properties):
|
||||
"""
|
||||
This creates a new menu widget. Unlike the menu statement or
|
||||
renpy.menu function, this menu widget is not enclosed in any sort
|
||||
@@ -299,24 +293,9 @@ def menu(menuitems,
|
||||
if this item is a non-selectable caption.
|
||||
"""
|
||||
|
||||
# menu is now a conglomeration of other widgets. And bully for it.
|
||||
return add(renpy.display.behavior.Menu(menuitems, **properties))
|
||||
|
||||
renpy.ui.vbox(style=style, **properties)
|
||||
|
||||
for label, val in menuitems:
|
||||
if val is None:
|
||||
renpy.ui.text(label, style=caption_style)
|
||||
else:
|
||||
renpy.ui.textbutton(label,
|
||||
style=choice_button_style,
|
||||
text_style=choice_style,
|
||||
clicked=renpy.ui.returns(val))
|
||||
|
||||
renpy.ui.close()
|
||||
|
||||
# return add(renpy.display.behavior.Menu(menuitems, **properties))
|
||||
|
||||
def input(default, length=None, allow=None, exclude='{}', **properties):
|
||||
def input(default, length=None, **properties):
|
||||
"""
|
||||
This creats a new input widget. This widget accepts textual input
|
||||
until the user hits enter, and then returns that text.
|
||||
@@ -325,15 +304,9 @@ def input(default, length=None, allow=None, exclude='{}', **properties):
|
||||
|
||||
@param length: If set, the maximum number of characters that will be
|
||||
returned by this input.
|
||||
|
||||
@param allow: If not None, then if an input character is not in this
|
||||
string, it is ignored.
|
||||
|
||||
@param exclude: If not None, then if an input character is in this
|
||||
set, it is ignored.
|
||||
"""
|
||||
|
||||
return add(renpy.display.behavior.Input(default, length=length, allow=allow, exclude=exclude, **properties))
|
||||
return add(renpy.display.behavior.Input(default, length=length, **properties))
|
||||
|
||||
def image(filename, **properties):
|
||||
"""
|
||||
@@ -344,33 +317,16 @@ def image(filename, **properties):
|
||||
return add(renpy.display.image.Image(filename, **properties))
|
||||
|
||||
def imagemap(ground, selected, hotspots, unselected=None,
|
||||
style='imagemap', button_style='imagemap_button',
|
||||
**properties):
|
||||
"""
|
||||
This is called to create imagemaps. Parameters are
|
||||
This is the widget that implements imagemaps. Parameters are
|
||||
roughtly the same as renpy.imagemap. The value of the hotspot is
|
||||
returned when ui.interact() returns.
|
||||
"""
|
||||
|
||||
rv = fixed(style=style, **properties)
|
||||
|
||||
if not unselected:
|
||||
unselected = ground
|
||||
|
||||
image(ground)
|
||||
|
||||
for x0, y0, x1, y1, result in hotspots:
|
||||
imagebutton(renpy.display.im.Crop(unselected, x0, y0, x1 - x0, y1 - y0),
|
||||
renpy.display.im.Crop(selected, x0, y0, x1 - x0, y1 - y0),
|
||||
clicked=returns(result),
|
||||
style=button_style,
|
||||
xpos=x0, xanchor='left',
|
||||
ypos=y0, yanchor='top',
|
||||
)
|
||||
|
||||
close()
|
||||
|
||||
return rv
|
||||
return add(renpy.display.image.ImageMap(ground, selected,
|
||||
hotspots, unselected,
|
||||
**properties))
|
||||
|
||||
|
||||
def button(clicked=None, **properties):
|
||||
|
||||
+9
-53
@@ -2,8 +2,7 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
@@ -21,21 +20,18 @@ import sys
|
||||
# Extra things used for distribution.
|
||||
import encodings.utf_8
|
||||
import encodings.zlib_codec
|
||||
import encodings.unicode_escape
|
||||
import encodings.string_escape
|
||||
import encodings.raw_unicode_escape
|
||||
|
||||
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
def main():
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
dirname = os.path.dirname(sys.argv[0])
|
||||
|
||||
if dirname:
|
||||
os.chdir(dirname)
|
||||
|
||||
if name.find(".") != -1:
|
||||
name = name[:name.find(".")]
|
||||
@@ -55,9 +51,6 @@ def main():
|
||||
op.add_option('--python', dest='python', default=None,
|
||||
help='Run the argument in the python interpreter.')
|
||||
|
||||
op.add_option('--leak', dest='leak', action='store_true', default=False,
|
||||
help='When the game exits, dumps a profile of memory usage.')
|
||||
|
||||
options, args = op.parse_args()
|
||||
|
||||
if options.python:
|
||||
@@ -113,45 +106,8 @@ def main():
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
if options.leak:
|
||||
memory_profile()
|
||||
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def memory_profile():
|
||||
|
||||
print "Memory Profile"
|
||||
print
|
||||
print "Showing all objects in memory at program termination."
|
||||
print
|
||||
|
||||
import gc
|
||||
gc.collect()
|
||||
|
||||
objs = gc.get_objects()
|
||||
|
||||
c = { } # count
|
||||
dead_renders = 0
|
||||
|
||||
for i in objs:
|
||||
t = type(i)
|
||||
c[t] = c.get(t, 0) + 1
|
||||
|
||||
if isinstance(i, renpy.display.render.Render):
|
||||
if i.dead:
|
||||
dead_renders += 1
|
||||
|
||||
|
||||
results = [ (count, ty) for ty, count in c.iteritems() ]
|
||||
results.sort()
|
||||
|
||||
for count, ty in results:
|
||||
print count, str(ty)
|
||||
|
||||
if dead_renders:
|
||||
print
|
||||
print "*** found", dead_renders, "dead Renders. ***"
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
run_game.py
|
||||
+1
-1
@@ -8,7 +8,7 @@ def got_node(n):
|
||||
if isinstance(n, renpy.ast.Say):
|
||||
print n.what
|
||||
|
||||
renpy.config.searchpath = [ 'game' ]
|
||||
renpy.config.searchpath = [ 'moonlight' ]
|
||||
|
||||
renpy.script.Script(got_node)
|
||||
|
||||
|
||||
+6
-10
@@ -1,6 +1,6 @@
|
||||
init:
|
||||
$ config.window_title = "Test Game"
|
||||
$ config.profile = False
|
||||
$ config.profile = True
|
||||
|
||||
$ config.debug_sound = True
|
||||
|
||||
@@ -22,11 +22,11 @@ init:
|
||||
# Styles.
|
||||
$ style.window.background = Frame("frame.png", 120, 25)
|
||||
|
||||
$ style.menu_choice_button.hover_sound = "chev1.wav"
|
||||
$ style.menu_choice_button.activate_sound = "chev2.wav"
|
||||
$ style.menu.hover_sound = "chev1.wav"
|
||||
$ style.menu.activate_sound = "chev2.wav"
|
||||
|
||||
$ style.imagemap_button.hover_sound = "chev3.wav"
|
||||
$ style.imagemap_button.activate_sound = "chev4.wav"
|
||||
$ style.imagemap.hover_sound = "chev3.wav"
|
||||
$ style.imagemap.activate_sound = "chev4.wav"
|
||||
|
||||
$ style.button.hover_sound = "chev6.wav"
|
||||
$ style.button.activate_sound = "chev7.wav"
|
||||
@@ -45,7 +45,7 @@ init:
|
||||
|
||||
image eileen anim = Animation("9a_happy.png", 0.25,
|
||||
"9a_vhappy.png", 0.25,
|
||||
"9a_concerned.png", 60.0)
|
||||
"9a_concerned.png")
|
||||
|
||||
image movie = Movie()
|
||||
|
||||
@@ -82,10 +82,6 @@ label main_menu:
|
||||
# Stuff that happens before the real main menu.
|
||||
|
||||
scene black
|
||||
|
||||
show eileen anim
|
||||
|
||||
$ renpy.pause()
|
||||
|
||||
|
||||
show text "American Bishoujo\nPresents" \
|
||||
|
||||
Reference in New Issue
Block a user