Compare commits
1 Commits
renpy-5.3.3
...
renpy-4.7
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f060bf3b0 |
+207
@@ -1,3 +1,210 @@
|
||||
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
|
||||
----------
|
||||
|
||||
The demo was updated to show some of the new features listed below.
|
||||
|
||||
There's now a third state that hovered widgets can go into. The
|
||||
activate state is enabled when a button is clicked or a menu choice is
|
||||
selected. The activate state can only be seen when a transition occurs
|
||||
immediately after a button is clicked or a menu choice is selected.
|
||||
|
||||
The margin and padding properties have been broken up, so it's now
|
||||
possible to specify margin and padding for the left, right, top, and
|
||||
bottom of a windows.
|
||||
|
||||
The biggest internal change in this version is the introduction of a
|
||||
layer system in Ren'Py. Transitions can now work on an individual
|
||||
layer (as well as the previous behavior of the whole screen), so it's
|
||||
possible to do things like sliding in and out the dialogue box.
|
||||
|
||||
It's now possible to use transitions when entering and leaving the
|
||||
game menu, by assigning a transition object to
|
||||
library.enter_transition and library.exit_transition, respectively.
|
||||
|
||||
The preferences screen can now be entered from user code, and is
|
||||
now part of the main menu.
|
||||
|
||||
Character objects now support an interact argument when called
|
||||
directly. If it's False, then no interaction occurs. This lets a
|
||||
character window become part of a larger screen.
|
||||
|
||||
Two new extras have been added:
|
||||
|
||||
[list]
|
||||
[*] button_menu.rpy changes the way menus are displayed. Each menu
|
||||
choice is displayed in its own button, roughly centered on the
|
||||
screen. A single menu caption is displayed as narration, if such
|
||||
a caption exists.
|
||||
|
||||
[*] overlay_menu.rpy changes the behavior of right click from
|
||||
showing the game menu to showing a bank of buttons that, when
|
||||
clicked, bring the user to the game menu.
|
||||
[/list]
|
||||
|
||||
The overlay code was overhauled. It's now not possible for an overlay
|
||||
function to return a list of widgets. Instead, the overlay functions
|
||||
are expected to add widgets to the screen using the ui
|
||||
functions. Calling the new function renpy.restart_interaction will,
|
||||
among other things, cause the overlay functions to be called again, if
|
||||
one wants to replace the overlay on the screen.
|
||||
|
||||
A new variable, config.overlay_during_wait, controls if overlays are
|
||||
shown during the execution of wait statements.
|
||||
|
||||
Init blocks of the same priority are now assured to run in the order
|
||||
that they appear in a script file.
|
||||
|
||||
The renpy.interact() function has been eliminated. Use ui.interact()
|
||||
instead, as it performs more error checking.
|
||||
|
||||
Fixed a bug in which some redraws were ignored, and another in which
|
||||
the input widget failed to eat characters. (So typing 'f' in an input
|
||||
widget would be passed through, and eventually cause the game to go to
|
||||
fullscreen mode. No longer.)
|
||||
|
||||
4.5's demo script had a bug that cause it to crash, after playing the
|
||||
MPEG-1 movie. It was a bug in the demo script, and not in Ren'Py
|
||||
proper. It's fixed.
|
||||
|
||||
New in 4.5
|
||||
----------
|
||||
|
||||
We now ship an extras directory, with interesting sample code. It
|
||||
includes:
|
||||
|
||||
[list]
|
||||
[*] fullscreen.rpy, containing code to automatically switch the game
|
||||
into fullscreen on the first run, while preserving the user's
|
||||
preference on later runs.
|
||||
|
||||
[*] 640x480.rpy, which shows how to customize Ren'Py for a game that
|
||||
runs at 640x480.
|
||||
|
||||
[*] kanamode.rpy, contains the code to emulate the interface of the
|
||||
various Digital Object games, which display text one line at a
|
||||
time, but keep a page of text on the screen at once.
|
||||
([url=http://www.bishoujo.us/hosted/kanamode.jpg]screenshot[/url])
|
||||
[/list]
|
||||
|
||||
Added a new CropMove transition. This single class is the root of not
|
||||
one, not two, but fourteen transitions, in the forms of wipes, slides,
|
||||
slideaways, and rectangular irises. You can see some of these
|
||||
transitions in action in the demo, which now has a new section,
|
||||
"What's new with Ren'Py?"
|
||||
|
||||
Added support for playing MPEG-1 movies. Rather than explain it all
|
||||
here, we'l just mention that you can read the new "Movies" subsection
|
||||
of the "Multimedia: Sound, Music, and Movies" section of the Ren'Py
|
||||
manual. We support both hardware-accelerated full-window movies and
|
||||
using a movie as a widget. This is also shown in the what's new
|
||||
section of Ren'Py.
|
||||
|
||||
Added ui.grid(). This is a widget that places its children in a grid.
|
||||
The children must have sizes that do not consider the amount of space
|
||||
available for the grid to work.
|
||||
|
||||
Added ui.pausebehavior(). This separates pausing from the saybehavior
|
||||
paving the way for uninterruptable pauses (a bad idea, IMO) and menus
|
||||
that dismiss themseleves after a certain amount of time.
|
||||
|
||||
A new function, renpy.exists(), can check to see if a given file can
|
||||
be found in the searchpath. This could be used, for example, to make a
|
||||
game that only tries to play music if an add-on music package is
|
||||
downloaded, and the files placed in the appropriate place.
|
||||
|
||||
Added the ability to bind mouse events to mouseup as well as
|
||||
mousedown, and changed some of the default bindings to be on mouseup
|
||||
rather than mousedown. If you make changes to config.keymaps, you'll
|
||||
need to change mouse_1 to mousedown_1, and so on for various other
|
||||
button numbers.
|
||||
|
||||
Moved the place we look for menu sounds from the style of the choices
|
||||
in the menu to the style of the menu itself, as that seems more
|
||||
rational. Also, gave menus their own style. (Can't believe I forgot
|
||||
that.)
|
||||
|
||||
Made a few changes to the library screens. First of all, we
|
||||
rationalized (to some extent, it's still pretty ugly) the names of the
|
||||
styles for library widgets. So some of that may have changed. We
|
||||
placed preferences and the yes/no dialogue inside windows, which can
|
||||
be styled by the user. We then routed all button and label creation
|
||||
through two functions, _button_factory and _label_factory. By
|
||||
overriding these functions, the user can (for example) replace the
|
||||
default textbuttons with imagebuttons.
|
||||
|
||||
Made 'f' toggle fullscreen at the main menu. This will help on virtual
|
||||
windows, where the mouse doesn't work right in fullscreen mode.
|
||||
|
||||
Improved rendering speed. Now, we aggressively cache things to
|
||||
minimize re-renders, and only draw to the screen the parts of the
|
||||
screen that have changed. In common cases, such as the case where only
|
||||
some text has changed, this can greately improve performance. In other
|
||||
cases (transitions), we have to redraw the entire screen anyway, so
|
||||
there's little improvement.
|
||||
|
||||
Fixed a bug in which hiding the UI was not working properly. (This
|
||||
broke the center mouse button behavior.)
|
||||
|
||||
New in 4.4.2
|
||||
------------
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
Copyright 2004-2005 Tom Rothamel <pytom@bishoujo.us>
|
||||
Copyright 2004-2005 PyTom <pytom@bishoujo.us>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation files
|
||||
|
||||
+158
-64
@@ -39,6 +39,7 @@ init -500:
|
||||
library.main_menu = [
|
||||
( "Start Game", "start" ),
|
||||
( "Continue Game", "_continue" ),
|
||||
( "Preferences", "_preferences" ),
|
||||
( "Quit Game", "_quit" ),
|
||||
]
|
||||
|
||||
@@ -63,6 +64,12 @@ init -500:
|
||||
# button.
|
||||
library.exit_sound = None
|
||||
|
||||
# Transition that occurs when entering the game menu.
|
||||
library.enter_transition = None
|
||||
|
||||
# Transition that occurs when leaving the game menu.
|
||||
library.exit_transition = None
|
||||
|
||||
# True if the skip indicator should be shown.
|
||||
library.skip_indicator = True
|
||||
|
||||
@@ -76,6 +83,61 @@ init -500:
|
||||
# True if we can save, false otherwise.
|
||||
_can_save = True
|
||||
|
||||
def _button_factory(label,
|
||||
type=None,
|
||||
selected=None,
|
||||
disabled=False,
|
||||
clicked=None,
|
||||
**properties):
|
||||
"""
|
||||
This function is called to create the various buttons used
|
||||
in the game menu. By overriding this function, one can
|
||||
(for example) replace the default textbuttons with image buttons.
|
||||
When it is called, it's expected to add a button to the screen.
|
||||
|
||||
@param label: The label of this button, before translation.
|
||||
|
||||
@param type: The type of the button. One of "mm" (main menu),
|
||||
"gm_nav" (game menu), "file_picker_nav", "yesno", or "prefs".
|
||||
|
||||
@param selected: True if the button is selected, False if not,
|
||||
or None if it doesn't matter.
|
||||
|
||||
@param disabled: True if the button is disabled, False if not.
|
||||
|
||||
@param clicked: A function that should be executed when the
|
||||
button is clicked.
|
||||
|
||||
@param properties: Addtional layout properties.
|
||||
"""
|
||||
|
||||
style = type
|
||||
|
||||
if selected:
|
||||
style += "_selected"
|
||||
|
||||
if disabled:
|
||||
style += "_disabled"
|
||||
|
||||
style = style + "_button"
|
||||
text_style = style + "_text"
|
||||
|
||||
ui.textbutton(_(label), style=style, text_style=text_style, clicked=clicked, **properties)
|
||||
|
||||
def _label_factory(label, type, **properties):
|
||||
"""
|
||||
This function is called to create a new label. It can be
|
||||
overridden by the user to change how these labels are created.
|
||||
|
||||
@param label: The label of the box.
|
||||
|
||||
@param type: "prefs" or "yesno".
|
||||
|
||||
@param properties: This may contain position properties.
|
||||
"""
|
||||
|
||||
ui.text(_(label), style=type + "_label", **properties)
|
||||
|
||||
# The function that's used to translate strings in the game menu.
|
||||
def _(s):
|
||||
"""
|
||||
@@ -87,30 +149,16 @@ init -500:
|
||||
else:
|
||||
return s
|
||||
|
||||
# Called to make a screenshot happen.
|
||||
def _screenshot():
|
||||
renpy.screenshot("screenshot.bmp")
|
||||
|
||||
# Are the windows currently hidden?
|
||||
_windows_hidden = False
|
||||
|
||||
# Hides the windows.
|
||||
def _hide_windows():
|
||||
global _windows_hidden
|
||||
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
try:
|
||||
_windows_hidden = True
|
||||
renpy.interact(renpy.SayBehavior())
|
||||
finally:
|
||||
_windows_hidden = False
|
||||
|
||||
|
||||
# Set up the default keymap.
|
||||
python hide:
|
||||
|
||||
# Called to make a screenshot happen.
|
||||
def screenshot():
|
||||
renpy.screenshot("screenshot.bmp")
|
||||
|
||||
def invoke_game_menu():
|
||||
renpy.play(library.enter_sound)
|
||||
renpy.call_in_new_context('_game_menu')
|
||||
@@ -121,12 +169,12 @@ init -500:
|
||||
# The default keymap.
|
||||
km = renpy.Keymap(
|
||||
rollback = renpy.rollback,
|
||||
screenshot = _screenshot,
|
||||
screenshot = screenshot,
|
||||
toggle_fullscreen = renpy.toggle_fullscreen,
|
||||
toggle_music = renpy.toggle_music,
|
||||
toggle_skip = toggle_skipping,
|
||||
game_menu = invoke_game_menu,
|
||||
hide_windows = _hide_windows,
|
||||
hide_windows = renpy.curried_call_in_new_context("_hide_windows")
|
||||
)
|
||||
|
||||
config.underlay = [ km ]
|
||||
@@ -142,11 +190,24 @@ init -500:
|
||||
ui.conditional("config.skipping")
|
||||
ui.text(_("Skip Mode"), style='skip_indicator')
|
||||
|
||||
return [ ]
|
||||
|
||||
config.overlay_functions.append(skip_indicator)
|
||||
|
||||
return
|
||||
|
||||
label _hide_windows:
|
||||
|
||||
if _windows_hidden:
|
||||
return
|
||||
|
||||
python:
|
||||
_windows_hidden = True
|
||||
ui.saybehavior()
|
||||
ui.interact(suppress_overlay=True)
|
||||
_windows_hidden = False
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
# This is the true starting point of the program. Sssh... Don't
|
||||
# tell anyone.
|
||||
@@ -166,8 +227,11 @@ label _main_menu:
|
||||
|
||||
label _library_main_menu:
|
||||
|
||||
scene
|
||||
|
||||
python hide:
|
||||
|
||||
ui.add(renpy.Keymap(toggle_fullscreen = renpy.toggle_fullscreen))
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='mm_root_window')
|
||||
@@ -177,13 +241,13 @@ label _library_main_menu:
|
||||
ui.vbox()
|
||||
|
||||
for text, label in library.main_menu:
|
||||
ui.textbutton(text, clicked=ui.returns(label))
|
||||
_button_factory(text, "mm", clicked=ui.returns(label))
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
store._result = renpy.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
store._result = ui.interact(suppress_overlay = True,
|
||||
suppress_underlay = True)
|
||||
|
||||
# Computed jump to the appropriate label.
|
||||
$ renpy.jump(_result)
|
||||
@@ -195,7 +259,19 @@ label _continue:
|
||||
$ _can_save = False
|
||||
$ _at_main_menu = True
|
||||
|
||||
$ renpy.call_in_new_context("_load_menu")
|
||||
$ renpy.call_in_new_context("_game_menu_load")
|
||||
|
||||
$ _can_save = True
|
||||
$ _at_main_menu = False
|
||||
|
||||
jump _library_main_menu
|
||||
|
||||
# Used to call the game menu.
|
||||
label _preferences:
|
||||
$ _can_save = False
|
||||
$ _at_main_menu = True
|
||||
|
||||
$ renpy.call_in_new_context("_game_menu_preferences")
|
||||
|
||||
$ _can_save = True
|
||||
$ _at_main_menu = False
|
||||
@@ -213,8 +289,7 @@ init -500:
|
||||
# This is used to store scratch data that's used by the
|
||||
# library, but shouldn't be saved out as part of the savegame.
|
||||
_scratch = object()
|
||||
|
||||
|
||||
|
||||
# This returns a window containing the game menu navigation
|
||||
# buttons, set up to jump to the appropriate screen sections.
|
||||
def _game_nav(selected):
|
||||
@@ -230,32 +305,24 @@ init -500:
|
||||
ui.vbox()
|
||||
|
||||
for key, label, target, enabled in library.game_menu:
|
||||
style="gm_nav_button"
|
||||
text_style="gm_nav_button_text"
|
||||
|
||||
if key == selected:
|
||||
style = 'gm_nav_selected_button'
|
||||
text_style = 'gm_nav_selected_button_text'
|
||||
|
||||
clicked = ui.jumps(target)
|
||||
disabled = False
|
||||
|
||||
if not eval(enabled):
|
||||
style = 'gm_nav_disabled_button'
|
||||
text_style = 'gm_nav_disabled_button_text'
|
||||
|
||||
clicked = lambda : None
|
||||
disabled = True
|
||||
clicked = ui.returns(None)
|
||||
|
||||
|
||||
ui.textbutton(_(label), style=style, text_style=text_style,
|
||||
clicked=clicked)
|
||||
_button_factory(label, "gm_nav", selected=(key==selected),
|
||||
disabled=disabled, clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
def _game_interact():
|
||||
|
||||
return renpy.interact(suppress_underlay=True,
|
||||
suppress_overlay=True)
|
||||
return ui.interact(suppress_underlay=True,
|
||||
suppress_overlay=True)
|
||||
|
||||
|
||||
def _render_new_slot(name, save):
|
||||
@@ -332,32 +399,29 @@ init -500:
|
||||
_game_nav(selected)
|
||||
|
||||
ui.window(style='file_picker_window')
|
||||
|
||||
ui.vbox() # whole thing.
|
||||
|
||||
# Draw the navigation.
|
||||
ui.hbox(padding=library.padding * 10, style='file_picker_navbox') # nav buttons.
|
||||
|
||||
def tb(cond, label, clicked):
|
||||
if cond:
|
||||
style = 'button'
|
||||
text_style = 'button_text'
|
||||
else:
|
||||
style = 'disabled_button'
|
||||
text_style = 'disabled_button_text'
|
||||
|
||||
ui.textbutton(label, style=style, text_style=text_style, clicked=clicked)
|
||||
|
||||
_button_factory(label, "file_picker_nav", disabled=not cond, clicked=clicked)
|
||||
|
||||
# Previous
|
||||
tb(fpi > 0, _('Previous'), ui.returns(("fpidelta", -1)))
|
||||
|
||||
# Quick Access
|
||||
for i in range(0, library.file_quick_access_pages):
|
||||
target = i * library.file_page_length
|
||||
tb(fpi != target, str(i + 1), ui.returns(("fpiset", target)))
|
||||
|
||||
# Next
|
||||
tb(True, _('Next'), ui.returns(("fpidelta", +1)))
|
||||
|
||||
ui.close() # nav buttons.
|
||||
# Done with nav buttons.
|
||||
ui.close()
|
||||
|
||||
# This draws a single slot.
|
||||
def entry(offset):
|
||||
i = fpi + offset
|
||||
|
||||
@@ -369,6 +433,7 @@ init -500:
|
||||
_render_savefile(name, saves[name], newest)
|
||||
|
||||
|
||||
# Actually draw a slot.
|
||||
ui.hbox() # slots
|
||||
|
||||
ui.vbox()
|
||||
@@ -402,9 +467,23 @@ init -500:
|
||||
|
||||
_game_nav(screen)
|
||||
|
||||
ui.text(message, style='yesno_prompt')
|
||||
ui.textbutton(_("Yes"), style='yesno_yes', clicked=ui.returns(True))
|
||||
ui.textbutton(_("No"), style='yesno_no', clicked=ui.returns(False))
|
||||
ui.window(style='yesno_window')
|
||||
ui.vbox(library.padding * 10, xpos=0.5, xanchor='center', ypos=0.5, yanchor='center')
|
||||
|
||||
_label_factory(message, "yesno", xpos=0.5, xanchor='center')
|
||||
|
||||
ui.grid(5, 1, xfill=True)
|
||||
|
||||
# The extra nulls are because we want equal whitespace surrounding
|
||||
# the two buttons. It should work as long as we have xfill=True
|
||||
ui.null()
|
||||
_button_factory("Yes", 'yesno', clicked=ui.returns(True), xpos=0.5, xanchor='center')
|
||||
ui.null()
|
||||
_button_factory("No", 'yesno', clicked=ui.returns(False), xpos=0.5, xanchor='center')
|
||||
ui.null()
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
return _game_interact()
|
||||
|
||||
@@ -427,21 +506,32 @@ init -500:
|
||||
|
||||
|
||||
# Factored this all into one place, to make our lives a bit easier.
|
||||
label _take_screenshot:
|
||||
label _enter_game_menu:
|
||||
scene
|
||||
$ renpy.movie_stop()
|
||||
$ renpy.take_screenshot((library.thumbnail_width, library.thumbnail_height))
|
||||
|
||||
if library.enter_transition:
|
||||
$ renpy.transition(library.enter_transition)
|
||||
|
||||
return
|
||||
|
||||
# Entry points from the game into menu-space.
|
||||
label _load_menu:
|
||||
call _take_screenshot
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu:
|
||||
call _take_screenshot
|
||||
label _game_menu_save:
|
||||
call _enter_game_menu
|
||||
jump _save_screen
|
||||
|
||||
label _game_menu_load:
|
||||
call _enter_game_menu
|
||||
jump _load_screen
|
||||
|
||||
label _game_menu_preferences:
|
||||
call _enter_game_menu
|
||||
jump _prefs_screen
|
||||
|
||||
label _confirm_quit:
|
||||
call _take_screenshot
|
||||
call _enter_game_menu
|
||||
jump _quit_screen
|
||||
|
||||
# Menu screens.
|
||||
@@ -503,6 +593,10 @@ label _noisy_return:
|
||||
|
||||
# Return to the game.
|
||||
label _return:
|
||||
|
||||
if library.exit_transition:
|
||||
$ renpy.transition(library.exit_transition)
|
||||
|
||||
return
|
||||
|
||||
# Random nice things to have.
|
||||
|
||||
+13
-14
@@ -46,28 +46,22 @@ init -450:
|
||||
if not values:
|
||||
return
|
||||
|
||||
ui.window(style='prefs_pref')
|
||||
ui.vbox(style='prefs_pref')
|
||||
ui.text(_(self.name), style='prefs_label')
|
||||
|
||||
_label_factory(self.name, "prefs")
|
||||
|
||||
cur = getattr(_preferences, self.field)
|
||||
|
||||
for name, value in values:
|
||||
|
||||
style = 'prefs_button'
|
||||
text_style = 'prefs_button_text'
|
||||
|
||||
if cur == value:
|
||||
style = 'prefs_selected_button'
|
||||
text_style = 'prefs_selected_button_text'
|
||||
|
||||
def clicked(value=value):
|
||||
setattr(_preferences, self.field, value)
|
||||
return True
|
||||
|
||||
ui.textbutton(_(name),
|
||||
style=style,
|
||||
text_style=text_style,
|
||||
clicked=clicked)
|
||||
_button_factory(name, "prefs",
|
||||
selected=cur==value,
|
||||
clicked=clicked)
|
||||
|
||||
ui.close()
|
||||
|
||||
@@ -123,16 +117,21 @@ label _prefs_screen:
|
||||
|
||||
_game_nav("prefs")
|
||||
|
||||
ui.vbox(library.padding * 3, style='prefs_left')
|
||||
ui.window(style='prefs_window')
|
||||
ui.grid(2, 1, xfill=True)
|
||||
|
||||
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
|
||||
for i in library.left_preferences:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.vbox(library.padding * 3, style='prefs_right')
|
||||
ui.vbox(library.padding * 3, xpos=0.5, xanchor='center')
|
||||
for i in library.right_preferences:
|
||||
i.render_preference()
|
||||
ui.close()
|
||||
|
||||
ui.close()
|
||||
|
||||
_game_interact()
|
||||
|
||||
jump _prefs_screen
|
||||
|
||||
+57
-51
@@ -14,7 +14,7 @@
|
||||
# to your script. No need to mess around here, it will just make your
|
||||
# life harder when a new version of Ren'Py is released.
|
||||
|
||||
init -250:
|
||||
init -1000:
|
||||
python hide:
|
||||
|
||||
style.create('default', None,
|
||||
@@ -36,6 +36,9 @@ init -250:
|
||||
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
|
||||
@@ -107,6 +110,8 @@ init -250:
|
||||
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,14 +127,17 @@ init -250:
|
||||
|
||||
# Styles that are used for menus.
|
||||
|
||||
style.create('menu', 'default',
|
||||
"(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.""")
|
||||
"(text) The style that is used to render a menu caption.")
|
||||
|
||||
style.create('menu_choice', 'default',
|
||||
"""(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
|
||||
style.menu_choice.idle_color = (0, 255, 255, 255) # cyan
|
||||
|
||||
style.create('menu_window', 'window',
|
||||
@@ -197,6 +205,7 @@ init -250:
|
||||
style.button_text.size = 24
|
||||
style.button_text.color = dark_cyan
|
||||
style.button_text.hover_color = bright_cyan
|
||||
style.button_text.activate_color = bright_cyan
|
||||
style.button_text.drop_shadow = (2, 2)
|
||||
|
||||
# Selected button.
|
||||
@@ -208,6 +217,7 @@ init -250:
|
||||
|
||||
style.selected_button_text.color = dark_red
|
||||
style.selected_button_text.hover_color = bright_red
|
||||
style.selected_button_text.activate_color = bright_red
|
||||
|
||||
# Disabled button.
|
||||
|
||||
@@ -222,6 +232,7 @@ init -250:
|
||||
|
||||
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.
|
||||
@@ -301,12 +312,17 @@ init -250:
|
||||
'(position) The position of the naviation (next/previous) buttons in the file picker.')
|
||||
|
||||
style.file_picker_navbox.xmargin = 10
|
||||
|
||||
style.create('file_picker_image', 'default',
|
||||
'(position) The position of the image in each file picker entry.')
|
||||
|
||||
style.file_picker_image.xminimum = 280
|
||||
style.create('file_picker_nav_button', 'button',
|
||||
'(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.')
|
||||
|
||||
@@ -318,6 +334,7 @@ init -250:
|
||||
|
||||
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))
|
||||
|
||||
style.create('file_picker_text', 'default',
|
||||
'(text) A base style for all text that is displayed in the file picker.')
|
||||
@@ -333,6 +350,7 @@ init -250:
|
||||
'(text) The style that is applied to the old indicator in the file pciker.')
|
||||
|
||||
style.file_picker_new.hover_color = bright_red
|
||||
style.file_picker_new.activate_color = bright_red
|
||||
style.file_picker_new.idle_color = dark_red
|
||||
style.file_picker_new.minwidth = 30
|
||||
style.file_picker_old.minwidth = 30
|
||||
@@ -343,78 +361,66 @@ init -250:
|
||||
style.create('file_picker_empty_slot', 'file_picker_text',
|
||||
'(text) The style that is used for the empty slot indicator in the file picker.')
|
||||
|
||||
style.create('yesno_prompt', 'default',
|
||||
style.create('yesno_label', 'default',
|
||||
'(text, position) The style used for the prompt in a yes/no dialog.')
|
||||
|
||||
style.yesno_prompt.xpos = 0.5
|
||||
style.yesno_prompt.xanchor = 'center'
|
||||
style.yesno_label.color = green
|
||||
|
||||
style.yesno_prompt.ypos = 0.25
|
||||
style.yesno_prompt.yanchor = 'center'
|
||||
style.create('yesno_button', 'button',
|
||||
'(window, hover) The style of yes/no buttons.')
|
||||
|
||||
style.yesno_prompt.color = green
|
||||
|
||||
style.create('yesno_yes', 'button',
|
||||
'(position) The position of the yes button on the screen.')
|
||||
|
||||
style.yesno_yes.xpos = 0.33
|
||||
style.yesno_yes.xanchor = 'center'
|
||||
style.yesno_yes.ypos = 0.33
|
||||
style.yesno_yes.yanchor = 'center'
|
||||
|
||||
style.create('yesno_no', 'button',
|
||||
'(position) The position of the no button on the screen.')
|
||||
|
||||
style.yesno_no.xpos = 0.66
|
||||
style.yesno_no.xanchor = 'center'
|
||||
style.yesno_no.ypos = 0.33
|
||||
style.yesno_no.yanchor = 'center'
|
||||
style.create('yesno_button_text', 'button_text',
|
||||
'(window, hover) The style of yes/no button text.')
|
||||
|
||||
style.create('yesno_window', 'default',
|
||||
'(window) The style of a window containing a yes/no dialogue.')
|
||||
|
||||
style.yesno_window.xfill = True
|
||||
style.yesno_window.yminimum = 0.5
|
||||
|
||||
# Preferences
|
||||
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(window, position) The position of the box containing an individual preference.')
|
||||
|
||||
style.prefs_pref.xpos = 0.5
|
||||
style.prefs_pref.xanchor = 'center'
|
||||
|
||||
style.create('prefs_label', 'default',
|
||||
'(text, position) The style that is applied to the label of a block of preferences.')
|
||||
|
||||
style.prefs_label.xpos = 0.5
|
||||
style.prefs_label.xanchor = "center"
|
||||
style.prefs_label.color = green
|
||||
|
||||
style.create('prefs_pref', 'default',
|
||||
'(position) The position of the box containing an individual preference.')
|
||||
|
||||
style.prefs_pref.xpos = 0.5
|
||||
style.prefs_pref.xanchor = 'center'
|
||||
|
||||
style.create('prefs_left', 'default',
|
||||
'(position) The position of the left column of preferences.')
|
||||
|
||||
style.prefs_left.xpos = 0.25
|
||||
style.prefs_left.xanchor = "center"
|
||||
style.prefs_left.ypos = 0.05
|
||||
style.prefs_left.yalign = "top"
|
||||
|
||||
style.create('prefs_right', 'default',
|
||||
'(position) The position of the right column of preferences.')
|
||||
|
||||
style.prefs_right.xpos = 0.75
|
||||
style.prefs_right.xanchor = "center"
|
||||
style.prefs_right.ypos = 0.05
|
||||
style.prefs_right.yalign = "top"
|
||||
|
||||
style.create('prefs_button', 'button',
|
||||
'(window, hover) The style of an unselected preferences button.')
|
||||
|
||||
style.prefs_button.xpos = 0.5
|
||||
style.prefs_button.xanchor = 'center'
|
||||
|
||||
style.create('prefs_button_text', 'button_text',
|
||||
'(text, hover) The style of the text of an unselected preferences button.')
|
||||
|
||||
style.create('prefs_selected_button', 'selected_button',
|
||||
'(window, hover) The style of a selected preferences button.')
|
||||
|
||||
style.prefs_selected_button.xpos = 0.5
|
||||
style.prefs_selected_button.xanchor = 'center'
|
||||
|
||||
style.create('prefs_selected_button_text', 'selected_button_text',
|
||||
'(text, hover) The style of the text of a selected preferences button.')
|
||||
|
||||
style.create('prefs_window', 'default',
|
||||
'(window, position) A window containing all preferences.')
|
||||
|
||||
style.prefs_window.xfill=True
|
||||
style.prefs_window.ypadding = 0.05
|
||||
|
||||
style.create('skip_indicator', 'default',
|
||||
'(text, position) The style of the text that is used to indicate that skipping is in progress.')
|
||||
|
||||
style.skip_indicator.xpos = 10
|
||||
style.skip_indicator.ypos = 10
|
||||
|
||||
|
||||
+56
-10
@@ -2,7 +2,8 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
@@ -24,12 +25,7 @@ import encodings.zlib_codec
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
|
||||
@@ -51,6 +47,9 @@ if __name__ == "__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:
|
||||
@@ -72,6 +71,16 @@ if __name__ == "__main__":
|
||||
|
||||
type, value, tb = sys.exc_info()
|
||||
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print >>f
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "-- Full Traceback ------------------------------------------------------------"
|
||||
print >>f
|
||||
|
||||
traceback.print_tb(tb, None, sys.stdout)
|
||||
traceback.print_tb(tb, None, f)
|
||||
|
||||
@@ -96,8 +105,45 @@ if __name__ == "__main__":
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
+822
@@ -0,0 +1,822 @@
|
||||
#!/usr/bin/python
|
||||
#
|
||||
# Perforce Defect Tracking Integration Project
|
||||
# <http://www.ravenbrook.com/project/p4dti/>
|
||||
#
|
||||
# COVERAGE.PY -- COVERAGE TESTING
|
||||
#
|
||||
# Gareth Rees, Ravenbrook Limited, 2001-12-04
|
||||
# Ned Batchelder, 2004-12-12
|
||||
# http://nedbatchelder.com/code/modules/coverage.html
|
||||
#
|
||||
#
|
||||
# 1. INTRODUCTION
|
||||
#
|
||||
# This module provides coverage testing for Python code.
|
||||
#
|
||||
# The intended readership is all Python developers.
|
||||
#
|
||||
# This document is not confidential.
|
||||
#
|
||||
# See [GDR 2001-12-04a] for the command-line interface, programmatic
|
||||
# interface and limitations. See [GDR 2001-12-04b] for requirements and
|
||||
# design.
|
||||
|
||||
"""Usage:
|
||||
|
||||
coverage.py -x MODULE.py [ARG1 ARG2 ...]
|
||||
Execute module, passing the given command-line arguments, collecting
|
||||
coverage data.
|
||||
|
||||
coverage.py -e
|
||||
Erase collected coverage data.
|
||||
|
||||
coverage.py -r [-m] FILE1 FILE2 ...
|
||||
Report on the statement coverage for the given files. With the -m
|
||||
option, show line numbers of the statements that weren't executed.
|
||||
|
||||
coverage.py -a [-d dir] FILE1 FILE2 ...
|
||||
Make annotated copies of the given files, marking statements that
|
||||
are executed with > and statements that are missed with !. With
|
||||
the -d option, make the copies in that directory. Without the -d
|
||||
option, make each copy in the same directory as the original.
|
||||
|
||||
Coverage data is saved in the file .coverage by default. Set the
|
||||
COVERAGE_FILE environment variable to save it somewhere else."""
|
||||
|
||||
__version__ = "2.2.20041231" # see detailed history at the end of this file.
|
||||
|
||||
import compiler
|
||||
import compiler.visitor
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import types
|
||||
|
||||
|
||||
# 2. IMPLEMENTATION
|
||||
#
|
||||
# This uses the "singleton" pattern.
|
||||
#
|
||||
# The word "morf" means a module object (from which the source file can
|
||||
# be deduced by suitable manipulation of the __file__ attribute) or a
|
||||
# filename.
|
||||
#
|
||||
# When we generate a coverage report we have to canonicalize every
|
||||
# filename in the coverage dictionary just in case it refers to the
|
||||
# module we are reporting on. It seems a shame to throw away this
|
||||
# information so the data in the coverage dictionary is transferred to
|
||||
# the 'cexecuted' dictionary under the canonical filenames.
|
||||
#
|
||||
# The coverage dictionary is called "c" and the trace function "t". The
|
||||
# reason for these short names is that Python looks up variables by name
|
||||
# at runtime and so execution time depends on the length of variables!
|
||||
# In the bottleneck of this application it's appropriate to abbreviate
|
||||
# names to increase speed.
|
||||
|
||||
# A dictionary with an entry for (Python source file name, line number
|
||||
# in that file) if that line has been executed.
|
||||
c = {}
|
||||
|
||||
# t(f, x, y). This method is passed to sys.settrace as a trace
|
||||
# function. See [van Rossum 2001-07-20b, 9.2] for an explanation of
|
||||
# sys.settrace and the arguments and return value of the trace function.
|
||||
# See [van Rossum 2001-07-20a, 3.2] for a description of frame and code
|
||||
# objects.
|
||||
|
||||
def t(f, w, a):
|
||||
if w == 'line':
|
||||
c[(f.f_code.co_filename, f.f_lineno)] = 1
|
||||
return t
|
||||
|
||||
class StatementFindingAstVisitor(compiler.visitor.ASTVisitor):
|
||||
def __init__(self, statements, excluded, suite_spots):
|
||||
compiler.visitor.ASTVisitor.__init__(self)
|
||||
self.statements = statements
|
||||
self.excluded = excluded
|
||||
self.suite_spots = suite_spots
|
||||
self.excluding_suite = 0
|
||||
|
||||
def doRecursive(self, node):
|
||||
self.recordNodeLine(node)
|
||||
for n in node.getChildNodes():
|
||||
self.dispatch(n)
|
||||
|
||||
visitStmt = visitModule = doRecursive
|
||||
|
||||
def doCode(self, node):
|
||||
if hasattr(node, 'decorators') and node.decorators:
|
||||
self.dispatch(node.decorators)
|
||||
self.doSuite(node, node.code)
|
||||
|
||||
visitFunction = visitClass = doCode
|
||||
|
||||
def getFirstLine(self, node):
|
||||
# Find the first line in the tree node.
|
||||
lineno = node.lineno
|
||||
for n in node.getChildNodes():
|
||||
f = self.getFirstLine(n)
|
||||
if lineno and f:
|
||||
lineno = min(lineno, f)
|
||||
else:
|
||||
lineno = lineno or f
|
||||
return lineno
|
||||
|
||||
def getLastLine(self, node):
|
||||
# Find the first line in the tree node.
|
||||
lineno = node.lineno
|
||||
for n in node.getChildNodes():
|
||||
lineno = max(lineno, self.getLastLine(n))
|
||||
return lineno
|
||||
|
||||
def doStatement(self, node):
|
||||
self.recordLine(self.getFirstLine(node))
|
||||
|
||||
visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \
|
||||
visitPrintnl = visitRaise = visitSubscript = \
|
||||
visitDecorators = \
|
||||
doStatement
|
||||
|
||||
def recordNodeLine(self, node):
|
||||
return self.recordLine(node.lineno)
|
||||
|
||||
def recordLine(self, lineno):
|
||||
# Returns a bool, whether the line is included or excluded.
|
||||
if lineno:
|
||||
# Multi-line tests introducing suites have to get charged to their
|
||||
# keyword.
|
||||
if lineno in self.suite_spots:
|
||||
lineno = self.suite_spots[lineno][0]
|
||||
# If we're inside an exluded suite, record that this line was
|
||||
# excluded.
|
||||
if self.excluding_suite:
|
||||
self.excluded[lineno] = 1
|
||||
return 0
|
||||
# If this line is excluded, or suite_spots maps this line to
|
||||
# another line that is exlcuded, then we're excluded.
|
||||
elif self.excluded.has_key(lineno) or \
|
||||
self.suite_spots.has_key(lineno) and \
|
||||
self.excluded.has_key(self.suite_spots[lineno][1]):
|
||||
return 0
|
||||
# Otherwise, this is an executable line.
|
||||
else:
|
||||
self.statements[lineno] = 1
|
||||
return 1
|
||||
return 0
|
||||
|
||||
default = recordNodeLine
|
||||
|
||||
def recordAndDispatch(self, node):
|
||||
self.recordNodeLine(node)
|
||||
self.dispatch(node)
|
||||
|
||||
def doSuite(self, intro, body, exclude=0):
|
||||
exsuite = self.excluding_suite
|
||||
if exclude or (intro and not self.recordNodeLine(intro)):
|
||||
self.excluding_suite = 1
|
||||
self.recordAndDispatch(body)
|
||||
self.excluding_suite = exsuite
|
||||
|
||||
def doPlainWordSuite(self, prevsuite, suite):
|
||||
# Finding the exclude lines for else's is tricky, because they aren't
|
||||
# present in the compiler parse tree. Look at the previous suite,
|
||||
# and find its last line. If any line between there and the else's
|
||||
# first line are excluded, then we exclude the else.
|
||||
lastprev = self.getLastLine(prevsuite)
|
||||
firstelse = self.getFirstLine(suite)
|
||||
for l in range(lastprev+1, firstelse):
|
||||
if self.suite_spots.has_key(l):
|
||||
self.doSuite(None, suite, exclude=self.excluded.has_key(l))
|
||||
break
|
||||
else:
|
||||
self.doSuite(None, suite)
|
||||
|
||||
def doElse(self, prevsuite, node):
|
||||
if node.else_:
|
||||
self.doPlainWordSuite(prevsuite, node.else_)
|
||||
|
||||
def visitFor(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doElse(node.body, node)
|
||||
|
||||
def visitIf(self, node):
|
||||
# The first test has to be handled separately from the rest.
|
||||
# The first test is credited to the line with the "if", but the others
|
||||
# are credited to the line with the test for the elif.
|
||||
self.doSuite(node, node.tests[0][1])
|
||||
for t, n in node.tests[1:]:
|
||||
self.doSuite(t, n)
|
||||
self.doElse(node.tests[-1][1], node)
|
||||
|
||||
def visitWhile(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doElse(node.body, node)
|
||||
|
||||
def visitTryExcept(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
for i in range(len(node.handlers)):
|
||||
a, b, h = node.handlers[i]
|
||||
if not a:
|
||||
# It's a plain "except:". Find the previous suite.
|
||||
if i > 0:
|
||||
prev = node.handlers[i-1][2]
|
||||
else:
|
||||
prev = node.body
|
||||
self.doPlainWordSuite(prev, h)
|
||||
else:
|
||||
self.doSuite(a, h)
|
||||
self.doElse(node.handlers[-1][2], node)
|
||||
|
||||
def visitTryFinally(self, node):
|
||||
self.doSuite(node, node.body)
|
||||
self.doPlainWordSuite(node.body, node.final)
|
||||
|
||||
def visitGlobal(self, node):
|
||||
# "global" statements don't execute like others (they don't call the
|
||||
# trace function), so don't record their line numbers.
|
||||
pass
|
||||
|
||||
the_coverage = None
|
||||
|
||||
class coverage:
|
||||
error = "coverage error"
|
||||
|
||||
# Name of the cache file (unless environment variable is set).
|
||||
cache_default = ".coverage"
|
||||
|
||||
# Environment variable naming the cache file.
|
||||
cache_env = "COVERAGE_FILE"
|
||||
|
||||
# A map from canonical Python source file name to a dictionary in
|
||||
# which there's an entry for each line number that has been
|
||||
# executed.
|
||||
cexecuted = {}
|
||||
|
||||
# Cache of results of calling the analysis2() method, so that you can
|
||||
# specify both -r and -a without doing double work.
|
||||
analysis_cache = {}
|
||||
|
||||
# Cache of results of calling the canonical_filename() method, to
|
||||
# avoid duplicating work.
|
||||
canonical_filename_cache = {}
|
||||
|
||||
def __init__(self):
|
||||
global the_coverage
|
||||
if the_coverage:
|
||||
raise self.error, "Only one coverage object allowed."
|
||||
self.usecache = 1
|
||||
self.cache = None
|
||||
self.exclude_re = ''
|
||||
|
||||
def help(self, error=None):
|
||||
if error:
|
||||
print error
|
||||
print
|
||||
print __doc__
|
||||
sys.exit(1)
|
||||
|
||||
def command_line(self):
|
||||
import getopt
|
||||
settings = {}
|
||||
optmap = {
|
||||
'-a': 'annotate',
|
||||
'-d:': 'directory=',
|
||||
'-e': 'erase',
|
||||
'-h': 'help',
|
||||
'-i': 'ignore-errors',
|
||||
'-m': 'show-missing',
|
||||
'-r': 'report',
|
||||
'-x': 'execute',
|
||||
}
|
||||
short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '')
|
||||
long_opts = optmap.values()
|
||||
options, args = getopt.getopt(sys.argv[1:], short_opts,
|
||||
long_opts)
|
||||
for o, a in options:
|
||||
if optmap.has_key(o):
|
||||
settings[optmap[o]] = 1
|
||||
elif optmap.has_key(o + ':'):
|
||||
settings[optmap[o + ':']] = a
|
||||
elif o[2:] in long_opts:
|
||||
settings[o[2:]] = 1
|
||||
elif o[2:] + '=' in long_opts:
|
||||
settings[o[2:]] = a
|
||||
else:
|
||||
self.help("Unknown option: '%s'." % o)
|
||||
if settings.get('help'):
|
||||
self.help()
|
||||
for i in ['erase', 'execute']:
|
||||
for j in ['annotate', 'report']:
|
||||
if settings.get(i) and settings.get(j):
|
||||
self.help("You can't specify the '%s' and '%s' "
|
||||
"options at the same time." % (i, j))
|
||||
args_needed = (settings.get('execute')
|
||||
or settings.get('annotate')
|
||||
or settings.get('report'))
|
||||
action = settings.get('erase') or args_needed
|
||||
if not action:
|
||||
self.help("You must specify at least one of -e, -x, -r, or -a.")
|
||||
if not args_needed and args:
|
||||
self.help("Unexpected arguments %s." % args)
|
||||
|
||||
self.get_ready()
|
||||
self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]')
|
||||
|
||||
if settings.get('erase'):
|
||||
self.erase()
|
||||
if settings.get('execute'):
|
||||
if not args:
|
||||
self.help("Nothing to do.")
|
||||
sys.argv = args
|
||||
self.start()
|
||||
import __main__
|
||||
sys.path[0] = os.path.dirname(sys.argv[0])
|
||||
execfile(sys.argv[0], __main__.__dict__)
|
||||
if not args:
|
||||
args = self.cexecuted.keys()
|
||||
ignore_errors = settings.get('ignore-errors')
|
||||
show_missing = settings.get('show-missing')
|
||||
directory = settings.get('directory=')
|
||||
if settings.get('report'):
|
||||
self.report(args, show_missing, ignore_errors)
|
||||
if settings.get('annotate'):
|
||||
self.annotate(args, directory, ignore_errors)
|
||||
|
||||
def use_cache(self, usecache):
|
||||
self.usecache = usecache
|
||||
|
||||
def get_ready(self):
|
||||
if self.usecache and not self.cache:
|
||||
self.cache = os.environ.get(self.cache_env, self.cache_default)
|
||||
self.restore()
|
||||
self.analysis_cache = {}
|
||||
|
||||
def start(self):
|
||||
self.get_ready()
|
||||
sys.settrace(t)
|
||||
|
||||
def stop(self):
|
||||
sys.settrace(None)
|
||||
|
||||
def erase(self):
|
||||
global c
|
||||
c = {}
|
||||
self.analysis_cache = {}
|
||||
self.cexecuted = {}
|
||||
if self.cache and os.path.exists(self.cache):
|
||||
os.remove(self.cache)
|
||||
self.exclude_re = ''
|
||||
|
||||
def exclude(self, re):
|
||||
if self.exclude_re:
|
||||
self.exclude_re += "|"
|
||||
self.exclude_re += "(" + re + ")"
|
||||
|
||||
# save(). Save coverage data to the coverage cache.
|
||||
|
||||
def save(self):
|
||||
if self.usecache and self.cache:
|
||||
self.canonicalize_filenames()
|
||||
cache = open(self.cache, 'wb')
|
||||
import marshal
|
||||
marshal.dump(self.cexecuted, cache)
|
||||
cache.close()
|
||||
|
||||
# restore(). Restore coverage data from the coverage cache (if it
|
||||
# exists).
|
||||
|
||||
def restore(self):
|
||||
global c
|
||||
c = {}
|
||||
self.cexecuted = {}
|
||||
assert self.usecache
|
||||
if not os.path.exists(self.cache):
|
||||
return
|
||||
try:
|
||||
cache = open(self.cache, 'rb')
|
||||
import marshal
|
||||
cexecuted = marshal.load(cache)
|
||||
cache.close()
|
||||
if isinstance(cexecuted, types.DictType):
|
||||
self.cexecuted = cexecuted
|
||||
except:
|
||||
pass
|
||||
|
||||
# canonical_filename(filename). Return a canonical filename for the
|
||||
# file (that is, an absolute path with no redundant components and
|
||||
# normalized case). See [GDR 2001-12-04b, 3.3].
|
||||
|
||||
def canonical_filename(self, filename):
|
||||
if not self.canonical_filename_cache.has_key(filename):
|
||||
f = filename
|
||||
if os.path.isabs(f) and not os.path.exists(f):
|
||||
f = os.path.basename(f)
|
||||
if not os.path.isabs(f):
|
||||
for path in [os.curdir] + sys.path:
|
||||
g = os.path.join(path, f)
|
||||
if os.path.exists(g):
|
||||
f = g
|
||||
break
|
||||
cf = os.path.normcase(os.path.abspath(f))
|
||||
self.canonical_filename_cache[filename] = cf
|
||||
return self.canonical_filename_cache[filename]
|
||||
|
||||
# canonicalize_filenames(). Copy results from "executed" to
|
||||
# "cexecuted", canonicalizing filenames on the way. Clear the
|
||||
# "executed" map.
|
||||
|
||||
def canonicalize_filenames(self):
|
||||
global c
|
||||
for filename, lineno in c.keys():
|
||||
f = self.canonical_filename(filename)
|
||||
if not self.cexecuted.has_key(f):
|
||||
self.cexecuted[f] = {}
|
||||
self.cexecuted[f][lineno] = 1
|
||||
c = {}
|
||||
|
||||
# morf_filename(morf). Return the filename for a module or file.
|
||||
|
||||
def morf_filename(self, morf):
|
||||
if isinstance(morf, types.ModuleType):
|
||||
if not hasattr(morf, '__file__'):
|
||||
raise self.error, "Module has no __file__ attribute."
|
||||
file = morf.__file__
|
||||
else:
|
||||
file = morf
|
||||
return self.canonical_filename(file)
|
||||
|
||||
# analyze_morf(morf). Analyze the module or filename passed as
|
||||
# the argument. If the source code can't be found, raise an error.
|
||||
# Otherwise, return a tuple of (1) the canonical filename of the
|
||||
# source code for the module, (2) a list of lines of statements
|
||||
# in the source code, and (3) a list of lines of excluded statements.
|
||||
|
||||
def analyze_morf(self, morf):
|
||||
if self.analysis_cache.has_key(morf):
|
||||
return self.analysis_cache[morf]
|
||||
filename = self.morf_filename(morf)
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if ext == '.pyc':
|
||||
if not os.path.exists(filename[0:-1]):
|
||||
raise self.error, ("No source for compiled code '%s'."
|
||||
% filename)
|
||||
filename = filename[0:-1]
|
||||
elif ext != '.py':
|
||||
raise self.error, "File '%s' not Python source." % filename
|
||||
source = open(filename, 'r')
|
||||
lines, excluded_lines = self.find_executable_statements(
|
||||
source.read(), exclude=self.exclude_re
|
||||
)
|
||||
source.close()
|
||||
result = filename, lines, excluded_lines
|
||||
self.analysis_cache[morf] = result
|
||||
return result
|
||||
|
||||
def get_suite_spots(self, tree, spots):
|
||||
import symbol, token
|
||||
for i in range(1, len(tree)):
|
||||
if type(tree[i]) == type(()):
|
||||
if tree[i][0] == symbol.suite:
|
||||
# Found a suite, look back for the colon and keyword.
|
||||
lineno_colon = lineno_word = None
|
||||
for j in range(i-1, 0, -1):
|
||||
if tree[j][0] == token.COLON:
|
||||
lineno_colon = tree[j][2]
|
||||
elif tree[j][0] == token.NAME:
|
||||
if tree[j][1] == 'elif':
|
||||
# Find the line number of the first non-terminal
|
||||
# after the keyword.
|
||||
t = tree[j+1]
|
||||
while t and token.ISNONTERMINAL(t[0]):
|
||||
t = t[1]
|
||||
if t:
|
||||
lineno_word = t[2]
|
||||
else:
|
||||
lineno_word = tree[j][2]
|
||||
break
|
||||
elif tree[j][0] == symbol.except_clause:
|
||||
# "except" clauses look like:
|
||||
# ('except_clause', ('NAME', 'except', lineno), ...)
|
||||
if tree[j][1][0] == token.NAME:
|
||||
lineno_word = tree[j][1][2]
|
||||
break
|
||||
if lineno_colon and lineno_word:
|
||||
# Found colon and keyword, mark all the lines
|
||||
# between the two with the two line numbers.
|
||||
for l in range(lineno_word, lineno_colon+1):
|
||||
spots[l] = (lineno_word, lineno_colon)
|
||||
self.get_suite_spots(tree[i], spots)
|
||||
|
||||
def find_executable_statements(self, text, exclude=None):
|
||||
# Find lines which match an exclusion pattern.
|
||||
excluded = {}
|
||||
suite_spots = {}
|
||||
if exclude:
|
||||
reExclude = re.compile(exclude)
|
||||
lines = text.split('\n')
|
||||
for i in range(len(lines)):
|
||||
if reExclude.search(lines[i]):
|
||||
excluded[i+1] = 1
|
||||
|
||||
import parser
|
||||
tree = parser.suite(text+'\n\n').totuple(1)
|
||||
self.get_suite_spots(tree, suite_spots)
|
||||
|
||||
# Use the compiler module to parse the text and find the executable
|
||||
# statements. We add newlines to be impervious to final partial lines.
|
||||
statements = {}
|
||||
ast = compiler.parse(text+'\n\n')
|
||||
visitor = StatementFindingAstVisitor(statements, excluded, suite_spots)
|
||||
compiler.walk(ast, visitor, walker=visitor)
|
||||
|
||||
lines = statements.keys()
|
||||
lines.sort()
|
||||
excluded_lines = excluded.keys()
|
||||
excluded_lines.sort()
|
||||
return lines, excluded_lines
|
||||
|
||||
# format_lines(statements, lines). Format a list of line numbers
|
||||
# for printing by coalescing groups of lines as long as the lines
|
||||
# represent consecutive statements. This will coalesce even if
|
||||
# there are gaps between statements, so if statements =
|
||||
# [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then
|
||||
# format_lines will return "1-2, 5-11, 13-14".
|
||||
|
||||
def format_lines(self, statements, lines):
|
||||
pairs = []
|
||||
i = 0
|
||||
j = 0
|
||||
start = None
|
||||
pairs = []
|
||||
while i < len(statements) and j < len(lines):
|
||||
if statements[i] == lines[j]:
|
||||
if start == None:
|
||||
start = lines[j]
|
||||
end = lines[j]
|
||||
j = j + 1
|
||||
elif start:
|
||||
pairs.append((start, end))
|
||||
start = None
|
||||
i = i + 1
|
||||
if start:
|
||||
pairs.append((start, end))
|
||||
def stringify(pair):
|
||||
start, end = pair
|
||||
if start == end:
|
||||
return "%d" % start
|
||||
else:
|
||||
return "%d-%d" % (start, end)
|
||||
import string
|
||||
return string.join(map(stringify, pairs), ", ")
|
||||
|
||||
# Backward compatibility with version 1.
|
||||
def analysis(self, morf):
|
||||
f, s, _, m, mf = self.analysis2(morf)
|
||||
return f, s, m, mf
|
||||
|
||||
def analysis2(self, morf):
|
||||
filename, statements, excluded = self.analyze_morf(morf)
|
||||
self.canonicalize_filenames()
|
||||
if not self.cexecuted.has_key(filename):
|
||||
self.cexecuted[filename] = {}
|
||||
missing = []
|
||||
for line in statements:
|
||||
if not self.cexecuted[filename].has_key(line):
|
||||
missing.append(line)
|
||||
return (filename, statements, excluded, missing,
|
||||
self.format_lines(statements, missing))
|
||||
|
||||
def morf_name(self, morf):
|
||||
if isinstance(morf, types.ModuleType):
|
||||
return morf.__name__
|
||||
else:
|
||||
return os.path.splitext(os.path.basename(morf))[0]
|
||||
|
||||
def report(self, morfs, show_missing=1, ignore_errors=0):
|
||||
if not isinstance(morfs, types.ListType):
|
||||
morfs = [morfs]
|
||||
max_name = max([5,] + map(len, map(self.morf_name, morfs)))
|
||||
fmt_name = "%%- %ds " % max_name
|
||||
fmt_err = fmt_name + "%s: %s"
|
||||
header = fmt_name % "Name" + " Stmts Exec Cover"
|
||||
fmt_coverage = fmt_name + "% 6d % 6d % 5d%%"
|
||||
if show_missing:
|
||||
header = header + " Missing"
|
||||
fmt_coverage = fmt_coverage + " %s"
|
||||
print header
|
||||
print "-" * len(header)
|
||||
total_statements = 0
|
||||
total_executed = 0
|
||||
for morf in morfs:
|
||||
name = self.morf_name(morf)
|
||||
try:
|
||||
_, statements, _, missing, readable = self.analysis2(morf)
|
||||
n = len(statements)
|
||||
m = n - len(missing)
|
||||
if n > 0:
|
||||
pc = 100.0 * m / n
|
||||
else:
|
||||
pc = 100.0
|
||||
args = (name, n, m, pc)
|
||||
if show_missing:
|
||||
args = args + (readable,)
|
||||
print fmt_coverage % args
|
||||
total_statements = total_statements + n
|
||||
total_executed = total_executed + m
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
if not ignore_errors:
|
||||
type, msg = sys.exc_info()[0:2]
|
||||
print fmt_err % (name, type, msg)
|
||||
if len(morfs) > 1:
|
||||
print "-" * len(header)
|
||||
if total_statements > 0:
|
||||
pc = 100.0 * total_executed / total_statements
|
||||
else:
|
||||
pc = 100.0
|
||||
args = ("TOTAL", total_statements, total_executed, pc)
|
||||
if show_missing:
|
||||
args = args + ("",)
|
||||
print fmt_coverage % args
|
||||
|
||||
# annotate(morfs, ignore_errors).
|
||||
|
||||
blank_re = re.compile("\\s*(#|$)")
|
||||
else_re = re.compile("\\s*else\\s*:\\s*(#|$)")
|
||||
|
||||
def annotate(self, morfs, directory=None, ignore_errors=0):
|
||||
for morf in morfs:
|
||||
try:
|
||||
filename, statements, excluded, missing, _ = self.analysis2(morf)
|
||||
self.annotate_file(filename, statements, excluded, missing, directory)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
if not ignore_errors:
|
||||
raise
|
||||
|
||||
def annotate_file(self, filename, statements, excluded, missing, directory=None):
|
||||
source = open(filename, 'r')
|
||||
if directory:
|
||||
dest_file = os.path.join(directory,
|
||||
os.path.basename(filename)
|
||||
+ ',cover')
|
||||
else:
|
||||
dest_file = filename + ',cover'
|
||||
dest = open(dest_file, 'w')
|
||||
lineno = 0
|
||||
i = 0
|
||||
j = 0
|
||||
covered = 1
|
||||
while 1:
|
||||
line = source.readline()
|
||||
if line == '':
|
||||
break
|
||||
lineno = lineno + 1
|
||||
while i < len(statements) and statements[i] < lineno:
|
||||
i = i + 1
|
||||
while j < len(missing) and missing[j] < lineno:
|
||||
j = j + 1
|
||||
if i < len(statements) and statements[i] == lineno:
|
||||
covered = j >= len(missing) or missing[j] > lineno
|
||||
if self.blank_re.match(line):
|
||||
dest.write(' ')
|
||||
elif self.else_re.match(line):
|
||||
# Special logic for lines containing only
|
||||
# 'else:'. See [GDR 2001-12-04b, 3.2].
|
||||
if i >= len(statements) and j >= len(missing):
|
||||
dest.write('! ')
|
||||
elif i >= len(statements) or j >= len(missing):
|
||||
dest.write('> ')
|
||||
elif statements[i] == missing[j]:
|
||||
dest.write('! ')
|
||||
else:
|
||||
dest.write('> ')
|
||||
elif lineno in excluded:
|
||||
dest.write('- ')
|
||||
elif covered:
|
||||
dest.write('> ')
|
||||
else:
|
||||
dest.write('! ')
|
||||
dest.write(line)
|
||||
source.close()
|
||||
dest.close()
|
||||
|
||||
# Singleton object.
|
||||
the_coverage = coverage()
|
||||
|
||||
# Module functions call methods in the singleton object.
|
||||
def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw)
|
||||
def start(*args, **kw): return the_coverage.start(*args, **kw)
|
||||
def stop(*args, **kw): return the_coverage.stop(*args, **kw)
|
||||
def erase(*args, **kw): return the_coverage.erase(*args, **kw)
|
||||
def exclude(*args, **kw): return the_coverage.exclude(*args, **kw)
|
||||
def analysis(*args, **kw): return the_coverage.analysis(*args, **kw)
|
||||
def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw)
|
||||
def report(*args, **kw): return the_coverage.report(*args, **kw)
|
||||
def annotate(*args, **kw): return the_coverage.annotate(*args, **kw)
|
||||
def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw)
|
||||
|
||||
# Save coverage data when Python exits. (The atexit module wasn't
|
||||
# introduced until Python 2.0, so use sys.exitfunc when it's not
|
||||
# available.)
|
||||
try:
|
||||
import atexit
|
||||
atexit.register(the_coverage.save)
|
||||
except ImportError:
|
||||
sys.exitfunc = the_coverage.save
|
||||
|
||||
# Command-line interface.
|
||||
if __name__ == '__main__':
|
||||
the_coverage.command_line()
|
||||
|
||||
|
||||
# A. REFERENCES
|
||||
#
|
||||
# [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees;
|
||||
# Ravenbrook Limited; 2001-12-04;
|
||||
# <http://www.garethrees.org/2001/12/04/python-coverage/>.
|
||||
#
|
||||
# [GDR 2001-12-04b] "Statement coverage for Python: design and
|
||||
# analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04;
|
||||
# <http://www.garethrees.org/2001/12/04/python-coverage/design.html>.
|
||||
#
|
||||
# [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)";
|
||||
# Guide van Rossum; 2001-07-20;
|
||||
# <http://www.python.org/doc/2.1.1/ref/ref.html>.
|
||||
#
|
||||
# [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum;
|
||||
# 2001-07-20; <http://www.python.org/doc/2.1.1/lib/lib.html>.
|
||||
#
|
||||
#
|
||||
# B. DOCUMENT HISTORY
|
||||
#
|
||||
# 2001-12-04 GDR Created.
|
||||
#
|
||||
# 2001-12-06 GDR Added command-line interface and source code
|
||||
# annotation.
|
||||
#
|
||||
# 2001-12-09 GDR Moved design and interface to separate documents.
|
||||
#
|
||||
# 2001-12-10 GDR Open cache file as binary on Windows. Allow
|
||||
# simultaneous -e and -x, or -a and -r.
|
||||
#
|
||||
# 2001-12-12 GDR Added command-line help. Cache analysis so that it
|
||||
# only needs to be done once when you specify -a and -r.
|
||||
#
|
||||
# 2001-12-13 GDR Improved speed while recording. Portable between
|
||||
# Python 1.5.2 and 2.1.1.
|
||||
#
|
||||
# 2002-01-03 GDR Module-level functions work correctly.
|
||||
#
|
||||
# 2002-01-07 GDR Update sys.path when running a file with the -x option,
|
||||
# so that it matches the value the program would get if it were run on
|
||||
# its own.
|
||||
#
|
||||
# 2004-12-12 NMB Significant code changes.
|
||||
# - Finding executable statements has been rewritten so that docstrings and
|
||||
# other quirks of Python execution aren't mistakenly identified as missing
|
||||
# lines.
|
||||
# - Lines can be excluded from consideration, even entire suites of lines.
|
||||
# - The filesystem cache of covered lines can be disabled programmatically.
|
||||
# - Modernized the code.
|
||||
#
|
||||
# 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior
|
||||
# and add 'analysis2'. Add a global for 'annotate', and factor it, adding
|
||||
# 'annotate_file'.
|
||||
#
|
||||
# 2004-12-31 NMB Allow for keyword arguments in the module global functions.
|
||||
#
|
||||
# C. COPYRIGHT AND LICENCE
|
||||
#
|
||||
# Copyright 2001 Gareth Rees. All rights reserved.
|
||||
# Copyright 2004 Ned Batchelder. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
# DAMAGE.
|
||||
#
|
||||
# $Id: coverage.py 5 2004-12-14 12:08:23Z ned $
|
||||
+278
-9
@@ -40,6 +40,29 @@ init:
|
||||
# clauses and statements.
|
||||
$ fade = Fade(.5, 0, .5) # Fade to black and back.
|
||||
$ dissolve = Dissolve(0.5)
|
||||
|
||||
$ wiperight = CropMove(1.0, "wiperight")
|
||||
$ wipeleft = CropMove(1.0, "wipeleft")
|
||||
$ wipeup = CropMove(1.0, "wipeup")
|
||||
$ wipedown = CropMove(1.0, "wipedown")
|
||||
|
||||
$ slideright = CropMove(1.0, "slideright")
|
||||
$ slideleft = CropMove(1.0, "slideleft")
|
||||
$ slideup = CropMove(1.0, "slideup")
|
||||
$ slidedown = CropMove(1.0, "slidedown")
|
||||
|
||||
$ slideawayright = CropMove(1.0, "slideawayright")
|
||||
$ slideawayleft = CropMove(1.0, "slideawayleft")
|
||||
$ slideawayup = CropMove(1.0, "slideawayup")
|
||||
$ slideawaydown = CropMove(1.0, "slideawaydown")
|
||||
|
||||
$ irisout = CropMove(1.0, "irisout")
|
||||
$ irisin = CropMove(1.0, "irisin")
|
||||
|
||||
# Select the transitions that are used when entering and exiting
|
||||
# the game menu.
|
||||
$ library.enter_transition = dissolve
|
||||
$ library.exit_transition = dissolve
|
||||
|
||||
# Now, we declare the images that are used in the program.
|
||||
|
||||
@@ -101,9 +124,14 @@ 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 dialoge.
|
||||
"Girl" "Hi, and welcome to the Ren'Py 4 demo program."
|
||||
# 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."
|
||||
|
||||
# This instantly replaces the very happy picture of Eileen with
|
||||
# one showing her merely happy. It demonstrates how the show
|
||||
@@ -162,6 +190,10 @@ label choices:
|
||||
call writing from _call_writing_1
|
||||
jump choices
|
||||
|
||||
"What's new with Ren'Py?":
|
||||
call whatsnew from _call_whatsnew_1
|
||||
jump choices
|
||||
|
||||
# This choice has a condition associated with it. It is only
|
||||
# displayed if the condition is true (in this case, if we have
|
||||
# selected at least one other choice has been chosen.)
|
||||
@@ -448,7 +480,7 @@ label find_out_more:
|
||||
e "If you have questions, the best place to ask them is the Ren'Py
|
||||
forum of the Lemmasoft forums."
|
||||
|
||||
e "Just go to http://www.lemmasoft.net/forums/, and click on
|
||||
e "Just go to http://lemmasoft.renai.us/forums/, and click on
|
||||
Ren'Py."
|
||||
|
||||
e "We thank Blue Lemma for hosting our forum."
|
||||
@@ -604,6 +636,47 @@ 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
|
||||
@@ -736,10 +809,206 @@ init:
|
||||
plan[editing] = value
|
||||
editing = None
|
||||
|
||||
|
||||
|
||||
return plan
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
init:
|
||||
image movie = Movie()
|
||||
|
||||
python:
|
||||
style.create('odd_window', 'say_window')
|
||||
style.odd_window.left_margin = 50
|
||||
style.odd_window.right_margin = 150
|
||||
style.odd_window.bottom_margin = 25
|
||||
|
||||
eodd = Character('Eileen', color=(200, 255, 200, 255), window_style='odd_window')
|
||||
|
||||
|
||||
label whatsnew:
|
||||
|
||||
show washington
|
||||
show eileen happy
|
||||
|
||||
e "I can give you a demonstration of some of the new features in
|
||||
Ren'Py, but you'll have to tell me what version you want to
|
||||
start with."
|
||||
|
||||
menu:
|
||||
"I'd like to start with 4.5.":
|
||||
jump whatsnew45
|
||||
|
||||
"I'd like to start with 4.6.":
|
||||
jump whatsnew46
|
||||
|
||||
"I'd like to start with 4.7.":
|
||||
jump whatsnew47
|
||||
|
||||
label whatsnew45:
|
||||
|
||||
show washington
|
||||
show eileen happy
|
||||
|
||||
e "While most of the improvements in Ren'Py 4.5 were behind the scenes,
|
||||
we can give you a demonstration of one of the new features."
|
||||
|
||||
e "There is now a new transition, CropMove, that can be used to
|
||||
provide a whole range of transition effects."
|
||||
|
||||
hide eileen with dissolve
|
||||
|
||||
e "I'll stand offscreen, so you can see some of its modes. I'll read
|
||||
out the mode name after each transiton."
|
||||
|
||||
scene whitehouse with wiperight
|
||||
|
||||
e "We first have wiperight..."
|
||||
|
||||
scene washington with wipeleft
|
||||
|
||||
e "...followed by wipeleft... "
|
||||
|
||||
scene whitehouse with wipeup
|
||||
|
||||
e "...wipeup..."
|
||||
|
||||
scene washington with wipedown
|
||||
|
||||
e "...and wipedown."
|
||||
|
||||
e "Next, the slides."
|
||||
|
||||
scene whitehouse with slideright
|
||||
|
||||
e "Slideright..."
|
||||
|
||||
scene washington with slideleft
|
||||
|
||||
e "...slideleft..."
|
||||
|
||||
scene whitehouse with slideup
|
||||
|
||||
e "...slideup..."
|
||||
|
||||
scene washington with slidedown
|
||||
|
||||
e "and slidedown."
|
||||
|
||||
e "We also have a couple of transitions that use a rectangular iris."
|
||||
|
||||
scene whitehouse with irisout
|
||||
|
||||
e "There's irisout..."
|
||||
|
||||
with None
|
||||
scene washington
|
||||
show eileen happy
|
||||
with irisin
|
||||
|
||||
e "... and irisin."
|
||||
|
||||
e "There are other transitions, such as various forms of
|
||||
slideaway. And if you can't find the transition for you, you
|
||||
can write a custom one."
|
||||
|
||||
e "It's enough to make you feel a bit dizzy."
|
||||
|
||||
e "Ren'Py 4.5 also includes the ability to show MPEG-1 movies as
|
||||
cutscenes or even backgrounds."
|
||||
|
||||
label ike:
|
||||
|
||||
if renpy.exists('Eisenhow1952.mpg'):
|
||||
|
||||
e "Since you downloaded the Eisenhower commercial, I can show
|
||||
it to you as a cutscene."
|
||||
|
||||
e "You can click to continue if it gets on your nerves too
|
||||
much."
|
||||
|
||||
$ renpy.movie_cutscene('Eisenhow1952.mpg', 63.0)
|
||||
|
||||
hide eileen
|
||||
show movie at Position(xpos=420, ypos=25, xanchor='left', yanchor='top')
|
||||
show eileen happy
|
||||
|
||||
$ renpy.movie_start_displayable('Eisenhow1952.mpg', (352, 240))
|
||||
|
||||
e "Ren'Py can even overlay rendered images on top of a movie,
|
||||
although that's more taxing for your CPU."
|
||||
|
||||
e "It's like I'm some sort of newscaster or something."
|
||||
|
||||
$ renpy.movie_stop()
|
||||
hide movie
|
||||
|
||||
else:
|
||||
|
||||
e "You haven't downloaded the Eisenhower commercial, so we
|
||||
can't demonstrate it."
|
||||
|
||||
label whatsnew46:
|
||||
|
||||
eodd "As of 4.6, we now support separate padding and margin for the
|
||||
left, right, top, and bottom sides of a window."
|
||||
|
||||
eodd "This means that a game can have oddly shaped windows without
|
||||
having to go beyond the style system."
|
||||
|
||||
e "We also introduced a new layer system, and the ability to have
|
||||
transitions affect only one layer."
|
||||
|
||||
e "Because of this we can do things like slide away a window..."
|
||||
|
||||
$ renpy.transition(slideawayup, 'transient')
|
||||
$ renpy.pause(1.5)
|
||||
$ renpy.transition(slidedown, 'transient')
|
||||
|
||||
e "... and slide it back in again."
|
||||
|
||||
e "Also new in this release is the ability to specify transitions
|
||||
that occur when you enter and exit the game menu."
|
||||
|
||||
e "Right click to see them, if you want."
|
||||
|
||||
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
|
||||
else I can help you with?"
|
||||
|
||||
return
|
||||
|
||||
|
||||
|
||||
+11
-2
@@ -43,12 +43,18 @@ def copy_tree(source, dest, should_copy=lambda fn : True, license=""):
|
||||
if "/CVS" in dirpath:
|
||||
continue
|
||||
|
||||
if "/.svn" in dirpath:
|
||||
continue
|
||||
|
||||
reldir = dirpath[len(source):]
|
||||
dstrel = dest + "/" + reldir
|
||||
|
||||
for i in dirnames:
|
||||
if i == "CVS":
|
||||
continue
|
||||
|
||||
if i == ".svn":
|
||||
continue
|
||||
|
||||
os.mkdir(dstrel + "/" + i)
|
||||
|
||||
@@ -99,13 +105,16 @@ def main():
|
||||
copy_tree("doc", target + "/doc",
|
||||
should_copy = lambda fn : fn in doc_files)
|
||||
|
||||
# Copy the game
|
||||
# Copy the game
|
||||
copy_tree(gamedir, target + "/game",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~") and not fn.endswith(".mpg"))
|
||||
|
||||
copy_tree("common", target + "/common",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
copy_tree("extras", target + "/extras",
|
||||
should_copy = lambda fn : not fn.startswith(".") and not fn.endswith("~"))
|
||||
|
||||
def cp(x, license=""):
|
||||
copy_file(x, target + "/" + x)
|
||||
|
||||
|
||||
@@ -84,6 +84,12 @@ def function(m):
|
||||
renpy.store.renpy = renpy.exports
|
||||
|
||||
func = eval(name, store)
|
||||
|
||||
if isinstance(func, renpy.curry.Curry):
|
||||
if func.callable == renpy.curry.Curry:
|
||||
func = func.args[0]
|
||||
else:
|
||||
func = func.callable
|
||||
|
||||
doc = func.__doc__
|
||||
|
||||
|
||||
+381
-127
@@ -359,7 +359,8 @@ 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. For example:
|
||||
while a number requires %(name)d. The interpolated value is quoted
|
||||
so that a text tag cannot be interpolated in by mistake. For example:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
@@ -540,12 +541,20 @@ menu what_to_do:
|
||||
<p>
|
||||
Without the ability to display images to the user, a visual
|
||||
novel would be a text adventure. Ren'Py controls image display
|
||||
by using a <def>scene list</def>, a list of things to be
|
||||
displayed to the user. Every time an interaction starts (that
|
||||
is, a line of dialogue or a menu is displayed), the things in
|
||||
the scene list are drawn to the screen, with the first being in
|
||||
by using a <def>layers</def>, each comprising a list of things to be
|
||||
displayed to the user. By default, there are three layers. The
|
||||
master layer is manipulated using the various image display
|
||||
statement defined in this section. The transient layer is used to
|
||||
display ui widgets, like windows containing dialogue and
|
||||
menus. Finally, an overlay layer exists to allow things to be
|
||||
overlaid on top of the screen.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Every time an interaction starts (for example, a line of dialogue or a menu is displayed), the things in
|
||||
the each of the layers is drawn to the screen, with the first being in
|
||||
the back and the last being in the front. A number of statements
|
||||
manipulate the scene list. Before we can explain them, however,
|
||||
manipulate the contents of the master layer. Before we can explain them, however,
|
||||
we should first define a few terms.
|
||||
</p>
|
||||
|
||||
@@ -607,16 +616,16 @@ init:
|
||||
<p>
|
||||
|
||||
The next display statement is the show statement, which takes an
|
||||
image specifier and displays it on the screen. If an image with
|
||||
the same tag as the image given in the spec already exists on
|
||||
the scene list, it is replaced with the newly displayed
|
||||
image specifier and displays it on the master layer. If an image with
|
||||
the same tag as the image given in the spec already exists in
|
||||
the master layer, it is replaced with the newly displayed
|
||||
Displayable. Otherwise, the new one is added to the end of the
|
||||
scene list (that is, closest to the user).
|
||||
master layer (that is, closest to the user).
|
||||
|
||||
</p><p>
|
||||
|
||||
If an at list is present, the image is transformed with the at
|
||||
list before being added to the display list.
|
||||
list before being added to the master layer.
|
||||
|
||||
</p><p>
|
||||
|
||||
@@ -630,7 +639,7 @@ init:
|
||||
<rule>scene_statement -> "scene" ( image_spec )?</rule>
|
||||
|
||||
<p>
|
||||
The scene statement first clears the scene_list. If the optional
|
||||
The scene statement first clears the master layer. If the optional
|
||||
image_spec is present, it is shown as if it was shown with the
|
||||
show statement. The best use for the image_spec on a scene
|
||||
command is show a background for the scene.
|
||||
@@ -665,8 +674,8 @@ e "But sometimes, I can get upset for no good reason."
|
||||
<rule>hide_statement -> "hide" image_spec</rule>
|
||||
|
||||
<p>
|
||||
The hide statement is used to remove an image from the scene
|
||||
list. The image spec is parsed for a tag, and any image matching
|
||||
The hide statement is used to remove an image from the master
|
||||
layer. The image spec is parsed for a tag, and any image matching
|
||||
that tag is remove from the scene. The at list is ignored (but
|
||||
should be valid or omitted), and transitions associated with the
|
||||
image spec are run.
|
||||
@@ -814,8 +823,8 @@ show eileen happy at left
|
||||
<!-- func renpy.ParameterizedText -->
|
||||
|
||||
<p>
|
||||
This is used to impelement the text image. The user may also want
|
||||
to instatiate their own ParameterizedText object (in an init
|
||||
This is used to implement the text image. The user may also want
|
||||
to instantiate their own ParameterizedText object (in an init
|
||||
block) if they want to have more than one bit of text on the
|
||||
screen at once, or if they want to change the style (and therefore
|
||||
the position) of that text.
|
||||
@@ -914,22 +923,22 @@ show ball at Move((0.0, 0.0), (1.0, 1.0), 10.0,
|
||||
<h3>Transitions</h3>
|
||||
|
||||
<p>
|
||||
By default, Ren'Py displays each scene list by replacing the old
|
||||
scene list with a new one. This is appropriate in general (such as
|
||||
for emotion changes), but it may be boring for large scene
|
||||
changes, such as a change in scene or a character entering or
|
||||
By default, Ren'Py displays each scene by replacing the old
|
||||
scene with a new one. This is appropriate in general (such as
|
||||
for emotion changes), but it may be boring for large
|
||||
changes, such as a change in location or a character entering or
|
||||
leaving the scene. Ren'Py supports transitions that control how
|
||||
changes to the scene lists are exposed to the user.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Transitions occur between the last scene list that was shown to
|
||||
the user, and the current scene list that has been updated using
|
||||
Transitions occur between the last scene that was shown to
|
||||
the user, and the current scene that has been updated using
|
||||
the scene, show, or hide statements. A transition takes both lists
|
||||
as input, and is responsible for displaying the transition between
|
||||
them to the user. Each transition runs for a given amount of time,
|
||||
but may be dismissed early by the user. Once a transition is
|
||||
shown, the scene list is considered shown for the purposes of
|
||||
shown, the scene is considered shown for the purposes of
|
||||
future transitions.
|
||||
</p>
|
||||
|
||||
@@ -1068,57 +1077,23 @@ e "How are you doing?" with dissolve
|
||||
</dl>
|
||||
|
||||
<p>
|
||||
Functions that return things that are useful as arguments to with
|
||||
statements or clauses are:
|
||||
The following are functions that return things useful as
|
||||
transitions. The user should not supply the new_widget or old_widget
|
||||
parameters, as these are supplied by Ren'Py when a transition
|
||||
begins.
|
||||
</p>
|
||||
|
||||
<function name="Fade" sig="(in_time, hold_time, out_time, color=(0, 0, 0))">
|
||||
|
||||
<p>
|
||||
This returns an object that can be used as an argument to a with
|
||||
statement to fade the old scene into a solid color, waits for a
|
||||
given amount of time, and then fades from the solid color into
|
||||
the new scene.
|
||||
</p>
|
||||
<!-- func Fade -->
|
||||
|
||||
<p>
|
||||
<param>in_time</param> - The amount of time that will be spent
|
||||
fading from the old scene to the solid color. A float, given as
|
||||
seconds.
|
||||
</p>
|
||||
<!-- func Dissolve -->
|
||||
|
||||
<p>
|
||||
<param>hold_time</param> - The amount of time that will be spent
|
||||
displaying the solid color. A float, given as seconds.
|
||||
</p>
|
||||
<!-- func CropMove -->
|
||||
|
||||
<p>
|
||||
<param>out_time</param> - The amount of time that will be spent
|
||||
fading from the solid color to the new scene. A float, given as
|
||||
seconds.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<param>color</param> - The solid color that will be faded
|
||||
to. This is an RGB triple, where each element is in the range 0
|
||||
to 255. This defaults to black.
|
||||
</p>
|
||||
|
||||
</function>
|
||||
|
||||
<function name="Dissolve" sig="(delay)">
|
||||
<p>
|
||||
This dissolves from the old scene to the new scene, by
|
||||
overlaying the new scene on top of the old scene and varying its
|
||||
alpha from 0 to 255.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<param>delay</param> - The amount of time the dissolve will
|
||||
take.
|
||||
</p>
|
||||
|
||||
</function>
|
||||
<p>
|
||||
Some transitions can also be applied to specific layers, using the
|
||||
renpy.transition function (documented below). Only transitions that
|
||||
are not completely opaque can be used in this way.
|
||||
</p>
|
||||
|
||||
<h3>Control Statements</h3>
|
||||
|
||||
@@ -1740,6 +1715,8 @@ e "Pleased to meet you, %(name)s."
|
||||
|
||||
<!-- func renpy.transition -->
|
||||
|
||||
<!-- func renpy.restart_interaction -->
|
||||
|
||||
<!-- func renpy.full_restart -->
|
||||
|
||||
<!-- func renpy.quit -->
|
||||
@@ -1748,6 +1725,8 @@ e "Pleased to meet you, %(name)s."
|
||||
|
||||
<!-- func renpy.windows -->
|
||||
|
||||
<!-- func renpy.exists -->
|
||||
|
||||
<!-- func renpy.clear_game_runtime -->
|
||||
|
||||
<!-- func renpy.get_game_runtime -->
|
||||
@@ -1778,10 +1757,14 @@ e "Pleased to meet you, %(name)s."
|
||||
|
||||
<p>
|
||||
Overlays are set up by adding to the config.overlay_functions list
|
||||
a python function which, when called, returns a list of
|
||||
Displayables that are added to the overlay. These functions are
|
||||
a python function which, when called, uses the ui functions to add
|
||||
widgets to the screen. By default, such widgets are added to the
|
||||
'overlay' layer, but a call to ui.layer() can change the layer
|
||||
to any of the layers listed in library.overlay_layers. These functions are
|
||||
called for each interaction, which allows the overlay to change to
|
||||
reflect the status of game variables.
|
||||
reflect the status of game variables. If a variable affecting the
|
||||
overlay changes during an interaction, renpy.restart_interaction()
|
||||
should be called to regenerate the overlay.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -1799,11 +1782,9 @@ init:
|
||||
python hide:
|
||||
def date_overlay():
|
||||
if date:
|
||||
return [ Image(date + ".png",
|
||||
xpos=1.0, xanchor="right",
|
||||
ypos=0.0, yanchor="top") ]
|
||||
else:
|
||||
return [ ]
|
||||
ui.image(date + ".png",
|
||||
xpos=1.0, xanchor="right",
|
||||
ypos=0.0, yanchor="top") ]
|
||||
|
||||
config.overlay_functions.append(date_overlay)
|
||||
</example>
|
||||
@@ -1817,7 +1798,7 @@ init:
|
||||
</p>
|
||||
|
||||
|
||||
<h3>Sound and Music Functions</h3>
|
||||
<h3>Multimedia: Sound, Music, and Movies</h3>
|
||||
|
||||
<h4>Sound</h4>
|
||||
|
||||
@@ -1895,6 +1876,79 @@ e "Just kidding."
|
||||
of old music when a new track is started.
|
||||
</p>
|
||||
|
||||
<h4>Movies</h4>
|
||||
|
||||
<p>
|
||||
As of version 4.5, Ren'Py is capable of playing MPEG-1 movies. This
|
||||
allows you to include video as part of your game. There are two ways
|
||||
that this can be done. Displaying video in fullscreen mode allows
|
||||
video to be scaled up to very large sizes, while displaying video in a
|
||||
displayable allows it to be blended with images and text.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Displaying a video fullscreen allows it to take advantage of hardware
|
||||
decode and scaling support. The downside of fullscreen mode is that
|
||||
nothing else can be displayed while the movie is being displayed.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The easiest way to display a movie fullscreen is to display it using
|
||||
the renpy.cutscene function. This function displays a movie for a
|
||||
specified length of time. When that time has elapsed, or when the user
|
||||
clicks to dismiss the movie, the movie ends and the function returns.
|
||||
</p>
|
||||
|
||||
<!-- func renpy.movie_cutscene -->
|
||||
|
||||
<p>
|
||||
Displaying a movie in a displayable is more difficult, but it allows
|
||||
the movie to be mixed with rendered text and graphics. A downside of
|
||||
this method is that the screen needs to be re-rendered with each
|
||||
frame, a process that may be unacceptibly slow when the screen is
|
||||
large. We don't recommend this when the game window is larger that
|
||||
640x480.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To display a movie in a displayable, we must start the movie and show
|
||||
a Movie displayable on the screen. The movie is then displayed on the
|
||||
screen along with any other widget. When we no longer want to show it,
|
||||
we need to stop the decoding of the movie and hide the displayable.
|
||||
</p>
|
||||
|
||||
<!-- func Movie -->
|
||||
|
||||
<!-- func renpy.movie_start_displayable -->
|
||||
|
||||
<!-- func renpy.movie_stop -->
|
||||
|
||||
<example>
|
||||
init:
|
||||
image movie = Movie
|
||||
|
||||
$ renpy.movie_start_displayable("bg.mpg", (640, 480), -1)
|
||||
scene movie
|
||||
show eileen happy
|
||||
|
||||
e "I'm standing in front of a movie."
|
||||
|
||||
e "Isn't that keen?"
|
||||
|
||||
hide movie
|
||||
$ renpy.movie_stop()
|
||||
</example>
|
||||
|
||||
<p>
|
||||
Whenever a movie is playing, it is expected to provide all sound that
|
||||
the user hears. Ren'Py disables sound hardware for the duration of the
|
||||
movie.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
$ renpy.cutscene("On_Your_Mark.mpg", 394)
|
||||
</example>
|
||||
|
||||
<h3>Configuration Variables</h3>
|
||||
|
||||
<p>
|
||||
@@ -2072,6 +2126,31 @@ load scripts, as scripts will be loaded before it can be set.
|
||||
screen.
|
||||
</var>
|
||||
|
||||
<var name="config.layers" value="[ 'master', 'transient', 'overlay' ]">
|
||||
This variable gives a list of all of the layers that Ren'Py knows
|
||||
about, in the order that they will be displayed to the screen. (The
|
||||
lowest layer is the first entry in the list.) Ren'Py uses the layers
|
||||
"master", "transient", and "overlay" internally, so they should
|
||||
always be in this list.
|
||||
</var>
|
||||
|
||||
<var name="config.transient_layers" value="[ 'transient' ]" >
|
||||
This variable gives a list of all of the transient layers. Transient
|
||||
layers are layers that are cleared after each interaction. "transient"
|
||||
should always be in this list.
|
||||
</var>
|
||||
|
||||
<var name="config.overlay_layers" value="[ 'overlay' ]" >
|
||||
This is a list of all of the overlay layers. Overlay layers are
|
||||
cleared before the overlay functions are called. "overlay" should
|
||||
always be in this list.
|
||||
</var>
|
||||
|
||||
<var name="config.overlay_during_wait" value="True" >
|
||||
True if we want overlays to be shown during wait statements, or False
|
||||
if we'd prefer that they be hidden during the wait statements.
|
||||
</var>
|
||||
|
||||
<var name="library.file_page_length" value="10">
|
||||
This is the number of save slots that are shown in the picker
|
||||
that's used by the load and save portions of the game menu. This
|
||||
@@ -2100,14 +2179,14 @@ the file picker.
|
||||
thumbnail is shown to the user.
|
||||
</var>
|
||||
|
||||
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("Quit Game", "_quit") ]'>
|
||||
<var name="library.main_menu" value='[ ( "Start Game", "start" ), ("Continue Game", "_continue"), ("Preferences", "_preferences"), ("Quit Game", "_quit") ]'>
|
||||
This is used to give the main menu that is shown to the user
|
||||
when the game first starts. It is a list of tuples, where the
|
||||
first element of each tuple is the title of the menu button, and
|
||||
the second element is a label that we jump to when that button
|
||||
is selected. Two useful labels to use here are "_continue",
|
||||
which brings up the game menu in load mode, and "_quit", which
|
||||
terminates Ren'Py.
|
||||
is selected. Some useful labels to use here are "_continue",
|
||||
which brings up the game menu in load mode, "_preferences", which brings up the preferences screen,
|
||||
and "_quit", which terminates Ren'Py.
|
||||
</var>
|
||||
|
||||
<var name="library.has_music" value="True">
|
||||
@@ -2145,6 +2224,16 @@ the file picker.
|
||||
If True, the library will display a skip indicator when skipping
|
||||
through the script.
|
||||
</var>
|
||||
|
||||
<var name="library.enter_transition" value="None">
|
||||
If not None, this variable should give a transition that will be
|
||||
used when entering the game menu.
|
||||
</var>
|
||||
|
||||
<var name="library.exit_transition" value="None">
|
||||
If not None, this variable should give a transition that will be
|
||||
performed when exiting the game menu.
|
||||
</var>
|
||||
|
||||
|
||||
</dl>
|
||||
@@ -2214,12 +2303,25 @@ init:
|
||||
|
||||
<prop name="color">
|
||||
The color in which the text will be displayed on the screen, as an
|
||||
RGBA tuple. Example: (255, 255, 255, 255)
|
||||
RGBA tuple. Due to a limitation of pygame, currently the alpha is
|
||||
ignored. 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. Example: True
|
||||
jagged.
|
||||
</prop>
|
||||
|
||||
<prop name="drop_shadow">
|
||||
@@ -2298,15 +2400,68 @@ indent to quoted dialogue.
|
||||
128))
|
||||
</prop>
|
||||
|
||||
<prop name="left_margin">
|
||||
The amount of transparent space left to the left of this window. If a
|
||||
floating point number, it is scaled to the available width.
|
||||
</prop>
|
||||
|
||||
<prop name="right_margin">
|
||||
The amount of transparent space left to the right of this window. If a
|
||||
floating point number, it is scaled to the available width.
|
||||
</prop>
|
||||
|
||||
<prop name="top_margin">
|
||||
The amount of transparent space left to the top of this window. If a
|
||||
floating point number, it is scaled to the available height.
|
||||
</prop>
|
||||
|
||||
<prop name="bottom_margin">
|
||||
The amount of transparent space left to the bottom of this window. If a
|
||||
floating point number, it is scaled to the available height.
|
||||
</prop>
|
||||
|
||||
<prop name="xmargin">
|
||||
The amount of transparent space that is left around this window in
|
||||
the x direction, in pixels.
|
||||
This is a convenient (and backwards compatible) way of setting
|
||||
left_margin and right_margin to the same value.
|
||||
</prop>
|
||||
|
||||
<prop name="ymargin">
|
||||
This is a convenient (and backwards compatible) way of setting
|
||||
top_margin and bottom_margin to the same value.
|
||||
</prop>
|
||||
|
||||
<prop name="left_padding">
|
||||
The amount of space left between the edge of the border and the
|
||||
left side of the contents of the window. If a float, it is scaled to the
|
||||
width of the window.
|
||||
</prop>
|
||||
|
||||
<prop name="right_padding">
|
||||
The amount of space left between the edge of the border and the
|
||||
right side of the contents of the window. If a float, it is scaled to the
|
||||
width of the window.
|
||||
</prop>
|
||||
|
||||
<prop name="top_padding">
|
||||
The amount of space left between the edge of the border and the
|
||||
top side of the contents of the window. If a float, it is scaled to the
|
||||
height of the window.
|
||||
</prop>
|
||||
|
||||
<prop name="bottom_padding">
|
||||
The amount of space left between the edge of the border and the
|
||||
bottom side of the contents of the window. If a float, it is scaled to the
|
||||
height of the window.
|
||||
</prop>
|
||||
|
||||
<prop name="xpadding">
|
||||
The amount of space left between the edge of the background of the
|
||||
window, and the edge of the content contained within this window,
|
||||
in the x direction, in pixels.
|
||||
A convenient (and backwards compatible) way of setting
|
||||
left_padding and right_padding to the same value.
|
||||
</prop>
|
||||
|
||||
<prop name="ypadding">
|
||||
A convenient (and backwards compatible) way of setting
|
||||
top_padding and bottom_padding to the same value.
|
||||
</prop>
|
||||
|
||||
<prop name="xfill">
|
||||
@@ -2314,32 +2469,23 @@ indent to quoted dialogue.
|
||||
x direction. If False, it will shrink to fit its contents.
|
||||
</prop>
|
||||
|
||||
<prop name="xminimum">
|
||||
The minimum size of this window in the x direction, including
|
||||
margins and padding. If the window would be smaller than this, it
|
||||
is grown to be at least this size.
|
||||
</prop>
|
||||
|
||||
<prop name="ymargin">
|
||||
The amount of transparent space that is left around this window in
|
||||
the y direction, in pixels.
|
||||
</prop>
|
||||
|
||||
<prop name="ypadding">
|
||||
The amount of space left between the edge of the background of the
|
||||
window, and the edge of the content contained within this window,
|
||||
in the y direction, in pixels.
|
||||
</prop>
|
||||
|
||||
<prop name="yfill">
|
||||
If True, the window will expand to fill all available space in the
|
||||
y direction. If False, it will shrink to fit its contents.
|
||||
</prop>
|
||||
|
||||
<prop name="xminimum">
|
||||
The minimum size of this window in the x direction, including
|
||||
margins and padding. If the window would be smaller than this, it
|
||||
is grown to be at least this size. If a floating point number, it is
|
||||
scaled to the available width.
|
||||
</prop>
|
||||
|
||||
<prop name="yminimum">
|
||||
The minimum size of this window in the y direction, including
|
||||
margins and padding. If the window would be smaller than this, it
|
||||
is grown to be at least this size.
|
||||
is grown to be at least this size. If a floating point number, it is
|
||||
scaled to the available height.
|
||||
</prop>
|
||||
|
||||
|
||||
@@ -2452,10 +2598,11 @@ The size of the left gutter of a clickable bar, in pixels.
|
||||
|
||||
<p>
|
||||
In Ren'Py, buttons and their contents support the idea of hovering:
|
||||
using different sets of properties when the mouse is over or not
|
||||
over the widget. If the mouse is over the widget, then that widget
|
||||
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 "hover_" or "idle_" (as appropriate) in a
|
||||
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
|
||||
@@ -2463,6 +2610,11 @@ The size of the left gutter of a clickable bar, in pixels.
|
||||
default.hover_background, default.background.)
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The only time an activated widget will be seen by the user is if a
|
||||
transition occurs immediately after the widget has been activated.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As an example, take assigning a background to buttons that changes
|
||||
when the button is hovered over.
|
||||
@@ -2564,6 +2716,95 @@ 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>
|
||||
@@ -2677,10 +2918,10 @@ unilaterally making something different from everyone else.
|
||||
|
||||
<p>
|
||||
Anyway, in Ren'Py keysyms are strings. The first kind of keysym is of
|
||||
the form 'mouse_#', for a number between 1 and 5. These keysyms are
|
||||
generated by mouse button presses or turns of the mouse wheel. For
|
||||
example, "mouse_1" is generally the left mouse button, while "mouse_4"
|
||||
is a turn of the the mouse wheel to the top.
|
||||
the form 'mouseup_#' or 'mousedown_#', for a number between 1 and 5. These keysyms are
|
||||
generated by mouse button presses, releases, or turns of the mouse wheel. For
|
||||
example, "mousedown_1" is generally a press of the left mouse button,
|
||||
"mouseup_1" is a release of that button, and "mousedown_4" is a turn of the the mouse wheel to the top.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
@@ -2700,34 +2941,35 @@ This type of keysym looks like "K_BACKSPACE", "K_RETURN", and "K_TAB".
|
||||
<p>
|
||||
To change a binding, update the appropriate list in
|
||||
config.keymap. The following code adds the 't' key to the list of
|
||||
keys that dismiss a say statement.
|
||||
keys that dismiss a say statement, and removes the space key from
|
||||
that list.
|
||||
</p>
|
||||
|
||||
<example>
|
||||
init:
|
||||
$ config.keymap['dismiss'].append('t')
|
||||
$ config.keymap['dismiss'].remove('K_SPACE')
|
||||
</example>
|
||||
|
||||
<p>
|
||||
The default keymap is contained inside the python code implementing
|
||||
Ren'Py, and as of version 4.3 is as follows:
|
||||
Ren'Py, and as of version 4.5 is as follows:
|
||||
</p>
|
||||
|
||||
<example>
|
||||
keymap = dict(
|
||||
|
||||
# Bindings present almost everywhere, unless explicitly
|
||||
# disabled.
|
||||
rollback = [ 'K_PAGEUP', 'mouse_4' ],
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouse_3' ],
|
||||
hide_windows = [ 'mouse_2' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mousedown_3' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
@@ -2736,23 +2978,23 @@ keymap = dict(
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouse_1' ],
|
||||
menu_mouseselect = [ 'mouseup_1' ],
|
||||
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
|
||||
menu_keyup = [ 'K_UP' ],
|
||||
menu_keydown = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouse_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Input.
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouse_1' ],
|
||||
bar_click = [ 'mouseup_1' ],
|
||||
|
||||
# These keys control skipping.
|
||||
skip = [ 'K_LCTRL', 'K_RCTRL' ],
|
||||
@@ -2790,7 +3032,7 @@ $ ui.textbutton("B", clicked=ui.returns("B"))
|
||||
$ ui.textbutton("C", clicked=ui.returns("C"))
|
||||
|
||||
$ ui.close()
|
||||
$ choice = renpy.interact(suppress_overlay=True)
|
||||
$ choice = ui.interact(suppress_overlay=True)
|
||||
</example>
|
||||
|
||||
<p>
|
||||
@@ -2835,6 +3077,12 @@ suffer from this problem, and so should probably be used in preference
|
||||
to lambda.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
By default, widgets are added to the 'transient' layer. This can be
|
||||
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>
|
||||
The following are the functions available in the ui module, which is
|
||||
automatically present in the game namespace.
|
||||
@@ -2854,6 +3102,8 @@ automatically present in the game namespace.
|
||||
|
||||
<!-- func ui.hbox -->
|
||||
|
||||
<!-- func ui.grid -->
|
||||
|
||||
<!-- func ui.fixed -->
|
||||
|
||||
<h4>No-Child Widgets and Psuedo-Widgets</h4>
|
||||
@@ -2874,12 +3124,16 @@ automatically present in the game namespace.
|
||||
|
||||
<!-- func ui.saybehavior -->
|
||||
|
||||
<!-- func ui.pausebehavior -->
|
||||
|
||||
<!-- func ui.keymousebehavior -->
|
||||
|
||||
<h4>Functions</h4>
|
||||
|
||||
<!-- func ui.interact -->
|
||||
|
||||
<!-- func ui.layer -->
|
||||
|
||||
<!-- func ui.close -->
|
||||
|
||||
<function name="ui.returns" sig="(value)">
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../extras/fullscreen.rpy
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../extras/kanamode.rpy
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../script.rpy
|
||||
@@ -0,0 +1,102 @@
|
||||
init -10:
|
||||
|
||||
$ config.searchpath.append('moonlight')
|
||||
|
||||
# Set up the size of the screen, and the window title.
|
||||
$ config.screen_width = 800
|
||||
$ config.screen_height = 600
|
||||
$ config.window_title = "A Ren'Py Extras Game"
|
||||
|
||||
# These are positions that can be used inside at clauses. We set
|
||||
# them up here so that they can be used throughout the program.
|
||||
$ left = Position(xpos=0.0, xanchor='left')
|
||||
$ right = Position(xpos=1.0, xanchor='right')
|
||||
$ center = Position()
|
||||
|
||||
# Likewise, we set up some transitions that we can use in with
|
||||
# clauses and statements.
|
||||
$ fade = Fade(.5, 0, .5) # Fade to black and back.
|
||||
$ dissolve = Dissolve(0.5)
|
||||
|
||||
$ wiperight = CropMove(1.0, "wiperight")
|
||||
$ wipeleft = CropMove(1.0, "wipeleft")
|
||||
$ wipeup = CropMove(1.0, "wipeup")
|
||||
$ wipedown = CropMove(1.0, "wipedown")
|
||||
|
||||
$ slideright = CropMove(1.0, "slideright")
|
||||
$ slideleft = CropMove(1.0, "slideleft")
|
||||
$ slideup = CropMove(1.0, "slideup")
|
||||
$ slidedown = CropMove(1.0, "slidedown")
|
||||
|
||||
$ slideawayright = CropMove(1.0, "slideawayright")
|
||||
$ slideawayleft = CropMove(1.0, "slideawayleft")
|
||||
$ slideawayup = CropMove(1.0, "slideawayup")
|
||||
$ slideawaydown = CropMove(1.0, "slideawaydown")
|
||||
|
||||
$ irisout = CropMove(1.0, "irisout")
|
||||
$ irisin = CropMove(1.0, "irisin")
|
||||
|
||||
# Now, we declare the images that are used in the program.
|
||||
|
||||
# Images.
|
||||
image black = Solid((0, 0, 0, 255))
|
||||
image white = Solid((255, 255, 255, 255))
|
||||
image yellow = Solid((255, 255, 200, 255))
|
||||
|
||||
# Opening Sequence.
|
||||
image bigbeach1 = Image("bigbeach1.jpg")
|
||||
image presents = Image("presents.png")
|
||||
|
||||
# Backgrounds.
|
||||
image beach1 = Image("beach1b.jpg")
|
||||
image beach1 mary = Image("beach1c.jpg")
|
||||
image beach1 title = Image("beach1a.jpg")
|
||||
|
||||
image beach2 = Image("beach2.jpg")
|
||||
image beach3 = Image("beach3.jpg")
|
||||
|
||||
image dawn1 = Image("dawn1.jpg")
|
||||
image dawn2 = Image("dawn2.jpg")
|
||||
|
||||
image library = Image("library.jpg")
|
||||
|
||||
# Ending 1.
|
||||
image transfer = Image("transfer.png")
|
||||
image moonpic = Image("moonpic.jpg")
|
||||
image nogirlpic = Image("nogirlpic.jpg")
|
||||
|
||||
|
||||
# Ending 3.
|
||||
image littlemary = Image("littlemary.jpg")
|
||||
|
||||
# Ending 4.
|
||||
image hospital1 = Image("hospital1.jpg")
|
||||
image hospital2 = Image("hospital2.jpg")
|
||||
image hospital3 = Image("hospital3.jpg")
|
||||
image heaven = Image("heaven.jpg")
|
||||
|
||||
# Endings Common
|
||||
image good_ending = Image("ending.jpg")
|
||||
image bad_ending = Image("badending.jpg")
|
||||
|
||||
# Mary.
|
||||
image mary dark confused smiling = Image("mary_dark_confused_smiling.png")
|
||||
image mary dark confused wistful = Image("mary_dark_confused_wistful.png")
|
||||
image mary dark crying = Image("mary_dark_crying.png")
|
||||
image mary dark laughing = Image("mary_dark_laughing.png")
|
||||
image mary dark sad = Image("mary_dark_sad.png")
|
||||
image mary dark smiling = Image("mary_dark_smiling.png")
|
||||
image mary dark vhappy = Image("mary_dark_vhappy.png")
|
||||
image mary dark wistful = Image("mary_dark_wistful.png")
|
||||
|
||||
image mary dawn confused smiling = Image("mary_dawn_confused_smiling.png")
|
||||
image mary dawn confused wistful = Image("mary_dawn_confused_wistful.png")
|
||||
image mary dawn crying = Image("mary_dawn_crying.png")
|
||||
image mary dawn laughing = Image("mary_dawn_laughing.png")
|
||||
image mary dawn sad = Image("mary_dawn_sad.png")
|
||||
image mary dawn smiling = Image("mary_dawn_smiling.png")
|
||||
image mary dawn vhappy = Image("mary_dawn_vhappy.png")
|
||||
image mary dawn wistful = Image("mary_dawn_wistful.png")
|
||||
|
||||
label start:
|
||||
jump example
|
||||
@@ -0,0 +1,21 @@
|
||||
# This file contains a minimal set of style changes needed to have
|
||||
# Ren'Py work with a game that's 640x480 in size.
|
||||
|
||||
init 1:
|
||||
# Change the screen width.
|
||||
$ config.screen_width = 640
|
||||
$ config.screen_height = 480
|
||||
|
||||
# Font sizes.
|
||||
$ style.default.size = 20
|
||||
$ style.button_text.size = 20
|
||||
$ style.file_picker_text.size = 14
|
||||
|
||||
# Perhaps change the fudge factor on windows, if line spacing
|
||||
# looks weird.
|
||||
if renpy.windows():
|
||||
$ style.default.line_height_fudge = -4
|
||||
|
||||
$ style.file_picker_entry.xminimum = 320
|
||||
$ library.thumbnail_width = 60
|
||||
$ library.thumbnail_height = 45
|
||||
@@ -0,0 +1,63 @@
|
||||
# In this file, we have an example of replacing the default menu with
|
||||
# a custom one. This new menu consists of a series of centered
|
||||
# buttons, with dialogue appearing in a narration window.
|
||||
|
||||
# You can try this one out by dropping it right into the game
|
||||
# directory.
|
||||
|
||||
# The code that implements button menus.
|
||||
init:
|
||||
python:
|
||||
style.create('menu_button', 'button')
|
||||
style.create('menu_button_text', 'button_text')
|
||||
|
||||
def menu(menuitems):
|
||||
|
||||
narration = None
|
||||
|
||||
ui.keymousebehavior()
|
||||
|
||||
ui.window(style='menu_window')
|
||||
ui.vbox(xanchor='center', xpos=0.5)
|
||||
|
||||
for label, value in menuitems:
|
||||
if value is None:
|
||||
narration = label
|
||||
continue
|
||||
|
||||
ui.textbutton(label,
|
||||
style="menu_button",
|
||||
text_style="menu_button_text",
|
||||
clicked=ui.returns(value))
|
||||
|
||||
ui.close()
|
||||
|
||||
if narration:
|
||||
narrator(narration, interact=False)
|
||||
|
||||
rv = ui.interact()
|
||||
renpy.checkpoint()
|
||||
return rv
|
||||
|
||||
# Styles to make button menus look good.
|
||||
init 1:
|
||||
python hide:
|
||||
style.menu_window.background = None
|
||||
style.menu_window.yminimum = 0
|
||||
style.menu_window.ypos = 0.40
|
||||
style.menu_window.yanchor = 'center'
|
||||
style.menu_window.xfill = True
|
||||
|
||||
style.menu_button.background = Solid((0, 0, 255, 128))
|
||||
style.menu_button.xfill = True
|
||||
style.menu_button.top_padding = 5
|
||||
style.menu_button.bottom_margin = 5
|
||||
|
||||
style.menu_button_text.xpos = 0.5
|
||||
style.menu_button_text.xanchor = 'center'
|
||||
|
||||
style.menu_button_text.hover_color = (255, 255, 0, 255)
|
||||
style.menu_button_text.idle_color = (255, 255, 255, 255)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# This file contains code to automatically switch the game into
|
||||
# fullscreen mode.
|
||||
|
||||
# Please note: Fullscreen mode is broken on some platforms (mostly
|
||||
# virtual windows machines like vmware and virtual PC). So having
|
||||
# this code automatically execute could make your game hard to use
|
||||
# on those platforms.
|
||||
#
|
||||
# The way we work around this is to tell users of these platforms
|
||||
# to press 'f' to toggle fullscreen mode. If the game's running in
|
||||
# windowed mode, the problems don't seem to appear.
|
||||
#
|
||||
# You should probably put words to that effect in some sort of
|
||||
# README file.
|
||||
|
||||
# The first time this code runs, the game switches into fullscreen.
|
||||
# After that, it respects the user's preference.
|
||||
|
||||
init:
|
||||
python:
|
||||
if not persistent.set_fullscreen:
|
||||
persistent.set_fullscreen = True
|
||||
_preferences.fullscreen = True
|
||||
@@ -0,0 +1,193 @@
|
||||
# This is an implementation of Kana mode, a mode in which multiple
|
||||
# lines of dialogue are shown at once, in a fullscreen window. A
|
||||
# single line of dialogue is placed onto the screen for each mouse
|
||||
# click. Calling the clear function clears the screen, ensuring that
|
||||
# the next line will appear at the top of the sceen.
|
||||
#
|
||||
# An example of using Kana mode follows the code that implements it.
|
||||
|
||||
|
||||
# This init block contains all of the code needed to implement
|
||||
# Kana mode. You probably don't want to change this... unless you
|
||||
# do. But all of the customization is found below.
|
||||
init -100:
|
||||
python:
|
||||
|
||||
# This is a list of KanaCharacter, line of dialogue tuples.
|
||||
kana_display_list = [ ]
|
||||
|
||||
# Spacings used.
|
||||
kana_vspacing = 0
|
||||
kana_hspacing = 0
|
||||
|
||||
def clear(arg=None):
|
||||
global kana_display_list
|
||||
kana_display_list = [ ]
|
||||
|
||||
def kana_show():
|
||||
|
||||
ui.window(style='say_window')
|
||||
ui.vbox(kana_vspacing)
|
||||
|
||||
for char, what in kana_display_list:
|
||||
char.show(what)
|
||||
|
||||
ui.close()
|
||||
|
||||
ui.saybehavior()
|
||||
ui.interact()
|
||||
renpy.checkpoint()
|
||||
|
||||
# Characters and the narrator should be instances of this
|
||||
# object.
|
||||
class KanaCharacter(object):
|
||||
|
||||
def __init__(self, who,
|
||||
what_prefix='"',
|
||||
what_suffix='"',
|
||||
who_style='say_label',
|
||||
what_style='say_dialogue',
|
||||
**properties):
|
||||
|
||||
self.who = who
|
||||
self.what_prefix = what_prefix
|
||||
self.what_suffix = what_suffix
|
||||
self.who_style = who_style
|
||||
self.what_style = what_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what):
|
||||
kana_display_list.append((self, what))
|
||||
kana_show()
|
||||
|
||||
def show(self, what):
|
||||
ui.hbox(kana_hspacing)
|
||||
ui.text(self.who, style=self.who_style,
|
||||
**self.properties)
|
||||
ui.text(self.what_prefix + what + self.what_suffix,
|
||||
style=self.what_style)
|
||||
ui.close()
|
||||
|
||||
|
||||
# This section updates the styles that are used by the Kana mode stuff
|
||||
# so that they work well with Kana mode. The user may want to change
|
||||
# these so that they look more appropriate.
|
||||
init:
|
||||
|
||||
# The space between lines of dialogue.
|
||||
$ kana_vspacing = 10
|
||||
$ kana_hspacing = 10
|
||||
|
||||
$ style.say_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.say_window.xfill = True
|
||||
$ style.say_window.yfill = True
|
||||
$ style.say_window.xmargin = 0
|
||||
$ style.say_window.ymargin = 0
|
||||
$ style.say_window.xpadding = 20
|
||||
$ style.say_window.ypadding = 20
|
||||
|
||||
$ style.say_label.minwidth = 100
|
||||
$ style.say_label.textalign = 1.0
|
||||
|
||||
# This just makes it look nicer.
|
||||
$ style.say_dialogue.rest_indent = 9
|
||||
|
||||
# Adjust the menu to match.
|
||||
$ style.menu_window.background = Solid((0, 0, 0, 96))
|
||||
$ style.menu_window.xfill = True
|
||||
$ style.menu_window.yfill = True
|
||||
$ style.menu_window.xmargin = 0
|
||||
$ style.menu_window.ymargin = 0
|
||||
$ style.menu_window.xpadding = 20
|
||||
$ style.menu_window.ypadding = 20
|
||||
|
||||
$ style.menu.xpos = 110
|
||||
|
||||
# And now, the example. This example can't be simply run, but instead
|
||||
# should serve as a guide to how to get Kana mode working in your own
|
||||
# game.
|
||||
|
||||
# In the init block, declare the characters as KanaCharacters. We also
|
||||
# want to declare the narrator as a special KanaCharacter.
|
||||
init:
|
||||
$ p = KanaCharacter("")
|
||||
$ g = KanaCharacter("Girl:", color=(255, 128, 128, 255))
|
||||
$ narrator = KanaCharacter("",
|
||||
what_prefix='',
|
||||
what_suffix='',
|
||||
what_style='say_thought')
|
||||
|
||||
|
||||
# Now, the actual script of the example. (An excerpt from Moonlight
|
||||
# Walks.) Notice how we place calls to clear in places where the
|
||||
# we want the screen to be cleared.
|
||||
|
||||
# This text is here to serve as an example, and shouldn't be used
|
||||
# in your game.
|
||||
label example:
|
||||
|
||||
show beach2
|
||||
show mary dark wistful
|
||||
|
||||
$ clear()
|
||||
|
||||
p "What can you tell me about your parents?"
|
||||
|
||||
g "Papa and Mama both came across the ocean as settlers when they
|
||||
were just children."
|
||||
|
||||
g "Papa fought in the war. When it was over, he married Mama, and
|
||||
they used his pension to move here from the mainland, and to
|
||||
build us a house."
|
||||
|
||||
g "Together, they farmed the land, and eventually they had
|
||||
children."
|
||||
|
||||
g "I had an older sister and a younger sister. I was the middle
|
||||
child."
|
||||
|
||||
$ clear()
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She paused for a second to collect her thoughts before
|
||||
continuing. This part was taking a strain on her."
|
||||
|
||||
g "When I was ten, an epidemic hit the island."
|
||||
|
||||
g "We came down with it, and so did all of the other families."
|
||||
|
||||
g "My family was too sick to move, but our neighbors, the Millers,
|
||||
sent some of their boys to get help from the mainland."
|
||||
|
||||
"I remember thinking that Miller was the last name of my aunt and
|
||||
uncle, and wondering if they could be related to me."
|
||||
|
||||
g "I don't know what happened to them, but I never heard from them
|
||||
again."
|
||||
|
||||
show mary dark crying
|
||||
|
||||
g "It lasted a week, and then it was over. My sisters... my
|
||||
parents... they all..."
|
||||
|
||||
g "Now I'm the only one left."
|
||||
|
||||
$ clear()
|
||||
|
||||
p "I'm sorry."
|
||||
|
||||
"I didn't know what else to say to a girl that had lost her
|
||||
family, and was obviously broken up about it."
|
||||
|
||||
show mary dark sad
|
||||
|
||||
"She nodded in response, wiped her tears, and we once again
|
||||
started walking in silence."
|
||||
|
||||
menu menu_1:
|
||||
"Show me the example again.":
|
||||
jump example
|
||||
|
||||
"I'm done. Let me go.":
|
||||
return
|
||||
@@ -0,0 +1,87 @@
|
||||
# This file replaces most of the game menu navigation with overlay
|
||||
# buttons that jump directly to various parts of the game menu.
|
||||
# Right clicking hides and shows the buttons, rather than calling
|
||||
# up the game menu directly.
|
||||
|
||||
init:
|
||||
python hide:
|
||||
|
||||
# overlay_menu is an object storing information about the
|
||||
# overlay menu state.
|
||||
store.overlay_menu = object()
|
||||
overlay_menu.shown = False
|
||||
|
||||
# overlay_menu is also a new layer, containing the overlay
|
||||
# menu.
|
||||
config.layers.append("overlay_menu")
|
||||
config.overlay_layers.append("overlay_menu")
|
||||
|
||||
|
||||
# This function actually draws the overlay menu.
|
||||
def overlay_menu_func():
|
||||
if overlay_menu.shown:
|
||||
|
||||
ui.layer("overlay_menu")
|
||||
|
||||
ui.vbox(xpos=1.0, xanchor="right", ypos=0.75, yanchor="bottom")
|
||||
|
||||
def button(label, target):
|
||||
ui.textbutton(label, clicked=renpy.curried_call_in_new_context(target))
|
||||
|
||||
button("Load Game", "_game_menu_load")
|
||||
button("Save Game", "_game_menu_save")
|
||||
button("Preferences", "_game_menu_preferences")
|
||||
|
||||
ui.close()
|
||||
ui.close()
|
||||
|
||||
|
||||
config.overlay_functions.append(overlay_menu_func)
|
||||
|
||||
|
||||
# This function toggles the visibility of the overlay menu.
|
||||
def overlay_menu_toggle():
|
||||
shown = not overlay_menu.shown
|
||||
overlay_menu.shown = shown
|
||||
|
||||
# How long should the transitions take?
|
||||
trans_delay = 0.5
|
||||
|
||||
# These transitions assume that the menu is placed on the
|
||||
# right side of the screen. This indicator gives the
|
||||
# fraction of the screen that participates in transitions.
|
||||
|
||||
trans_frac = 0.75
|
||||
|
||||
if shown:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
startcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
startpos=(1.0, 0.0),
|
||||
endcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
endpos=(trans_frac, 0.0),
|
||||
topnew=True)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
else:
|
||||
|
||||
trans = CropMove(trans_delay,
|
||||
"custom",
|
||||
endcrop=(trans_frac, 0.0, 0.0, 1.0),
|
||||
endpos=(1.0, 0.0),
|
||||
startcrop=(trans_frac, 0.0, 1.0-trans_frac, 1.0),
|
||||
startpos=(trans_frac, 0.0),
|
||||
topnew=False)
|
||||
|
||||
renpy.transition(trans, 'overlay_menu')
|
||||
|
||||
|
||||
renpy.restart_interaction()
|
||||
|
||||
|
||||
# Add a new underlay that handles the overlay menu toggle.
|
||||
config.underlay.append(renpy.Keymap(game_menu = overlay_menu_toggle))
|
||||
|
||||
|
||||
+7
-7
@@ -2,9 +2,9 @@
|
||||
# order.
|
||||
|
||||
# Some version numbers and things.
|
||||
version = "Ren'Py 4.4.2"
|
||||
script_version = 5
|
||||
savegame_suffix = "-3.save"
|
||||
version = "Ren'Py 4.7"
|
||||
script_version = 7
|
||||
savegame_suffix = "-7.save"
|
||||
|
||||
|
||||
# Can be first, because has no dependencies, and may be imported
|
||||
@@ -19,21 +19,21 @@ import renpy.curry
|
||||
import renpy.execution
|
||||
import renpy.loader
|
||||
import renpy.loadsave
|
||||
import renpy.music
|
||||
import renpy.parser
|
||||
import renpy.python # object
|
||||
import renpy.script
|
||||
import renpy.style
|
||||
import renpy.sound
|
||||
|
||||
import renpy.display
|
||||
import renpy.display
|
||||
import renpy.display.render # Most display stuff depends on this.
|
||||
import renpy.display.core # object
|
||||
import renpy.display.surface
|
||||
import renpy.display.audio
|
||||
import renpy.display.text # core
|
||||
import renpy.display.layout # core
|
||||
import renpy.display.behavior # layout
|
||||
import renpy.display.transition # core
|
||||
import renpy.display.image # core, behavior
|
||||
import renpy.display.video
|
||||
|
||||
import renpy.ui
|
||||
|
||||
|
||||
+4
-12
@@ -1,3 +1,4 @@
|
||||
|
||||
# This file contains the AST for the Ren'Py script language. Each class
|
||||
# here corresponds to a statement in the script language.
|
||||
|
||||
@@ -117,7 +118,6 @@ 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.game.store)
|
||||
renpy.exports.say(who, self.what)
|
||||
|
||||
return self.next
|
||||
|
||||
@@ -298,6 +298,7 @@ def predict_imspec(imspec, callback):
|
||||
return
|
||||
|
||||
im = renpy.exports.images[imspec[0]]
|
||||
|
||||
im.predict(callback)
|
||||
|
||||
|
||||
@@ -403,16 +404,7 @@ class With(Node):
|
||||
def execute(self):
|
||||
trans = renpy.python.py_eval(self.expr)
|
||||
|
||||
# Code copied into exports.with
|
||||
|
||||
if not trans:
|
||||
renpy.game.interface.with_none()
|
||||
else:
|
||||
if renpy.game.preferences.transitions:
|
||||
renpy.game.interface.set_transition(trans)
|
||||
renpy.game.interface.interact(show_mouse=False,
|
||||
trans_pause=True,
|
||||
suppress_overlay=True)
|
||||
renpy.exports.with(trans)
|
||||
|
||||
return self.next
|
||||
|
||||
|
||||
+26
-10
@@ -98,21 +98,36 @@ fade_music = 0.0
|
||||
# Should the at list be sticky?
|
||||
sticky_positions = False
|
||||
|
||||
# A list of all of the layers that we know about.
|
||||
layers = [ 'master', 'transient', 'overlay' ]
|
||||
|
||||
# A list of layers that should be cleared when we replace
|
||||
# transients.
|
||||
transient_layers = [ 'transient' ]
|
||||
|
||||
# A list of layers that should be cleared when we recompute
|
||||
# overlays.
|
||||
overlay_layers = [ 'overlay' ]
|
||||
|
||||
# True if we want to show overlays during wait statements, or
|
||||
# false otherwise.
|
||||
overlay_during_wait = True
|
||||
|
||||
# The keymap that is used to change keypresses and mouse events.
|
||||
keymap = dict(
|
||||
|
||||
# Bindings present almost everywhere, unless explicitly
|
||||
# disabled.
|
||||
rollback = [ 'K_PAGEUP', 'mouse_4' ],
|
||||
rollback = [ 'K_PAGEUP', 'mousedown_4' ],
|
||||
screenshot = [ 's' ],
|
||||
toggle_fullscreen = [ 'f' ],
|
||||
toggle_music = [ 'm' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouse_3' ],
|
||||
hide_windows = [ 'mouse_2' ],
|
||||
game_menu = [ 'K_ESCAPE', 'mouseup_3' ],
|
||||
hide_windows = [ 'mouseup_2' ],
|
||||
|
||||
# Say.
|
||||
rollforward = [ 'mouse_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouse_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
rollforward = [ 'mousedown_5', 'K_PAGEDOWN' ],
|
||||
dismiss = [ 'mouseup_1', 'K_RETURN', 'K_SPACE', 'K_KP_ENTER' ],
|
||||
|
||||
# Keymouse.
|
||||
keymouse_left = [ 'K_LEFT' ],
|
||||
@@ -121,23 +136,23 @@ keymap = dict(
|
||||
keymouse_down = [ 'K_DOWN' ],
|
||||
|
||||
# Menu.
|
||||
menu_mouseselect = [ 'mouse_1' ],
|
||||
menu_mouseselect = [ 'mouseup_1' ],
|
||||
menu_keyselect = ['K_RETURN', 'K_KP_ENTER' ],
|
||||
menu_keyup = [ 'K_UP' ],
|
||||
menu_keydown = [ 'K_DOWN' ],
|
||||
|
||||
# Button.
|
||||
button_select = [ 'mouse_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
button_select = [ 'mouseup_1', 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Input.
|
||||
input_backspace = [ 'K_BACKSPACE' ],
|
||||
input_enter = [ 'K_RETURN', 'K_KP_ENTER' ],
|
||||
|
||||
# Imagemap.
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouse_1' ],
|
||||
imagemap_select = [ 'K_RETURN', 'K_KP_ENTER', 'mouseup_1' ],
|
||||
|
||||
# Bar.
|
||||
bar_click = [ 'mouse_1' ],
|
||||
bar_click = [ 'mouseup_1' ],
|
||||
|
||||
|
||||
# These keys control skipping.
|
||||
@@ -145,7 +160,8 @@ keymap = dict(
|
||||
toggle_skip = [ 'K_TAB' ],
|
||||
)
|
||||
|
||||
|
||||
# The number of frames that Ren'Py has shown.
|
||||
frames = 0
|
||||
|
||||
def backup():
|
||||
|
||||
|
||||
+2
-2
@@ -4,8 +4,8 @@ class Curry(object):
|
||||
callable with the stored arguments and the additional arguments
|
||||
supplied to the call.
|
||||
"""
|
||||
|
||||
__doc__ = property(fget=lambda self : self.callable.__doc__)
|
||||
|
||||
# __doc__ = property(fget=lambda self : self.callable.__doc__)
|
||||
|
||||
def __init__(self, callable, *args, **kwargs):
|
||||
self.callable = callable
|
||||
|
||||
@@ -0,0 +1,386 @@
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
|
||||
# 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():
|
||||
|
||||
global mixer_works
|
||||
global read_raw_volume
|
||||
global compute_midi_msf
|
||||
global set_music_volume
|
||||
global playing_midi
|
||||
|
||||
if mixer_works is not None:
|
||||
return
|
||||
|
||||
try:
|
||||
pygame.mixer.get_volume()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
mixer_works = False
|
||||
|
||||
mixer_works = True
|
||||
|
||||
windows_magic = False
|
||||
|
||||
if hasattr(sys, 'winver') and mixer_works:
|
||||
|
||||
try:
|
||||
from ctypes import windll, c_uint, byref
|
||||
winmm = windll.winmm
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
return True
|
||||
|
||||
compute_midi_msf = _compute_midi_msf
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
global master_music_volume
|
||||
master_music_volume = vol
|
||||
|
||||
try:
|
||||
pygame.mixer.music.set_volume(vol)
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
set_music_volume = _set_music_volume
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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()
|
||||
|
||||
def restore_music():
|
||||
"""
|
||||
This makes sure that the current music matches the music found in
|
||||
the context.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
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 mixer_works:
|
||||
return
|
||||
|
||||
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
|
||||
|
||||
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 disable_mixer():
|
||||
"""
|
||||
This function is called by the video code to disable the
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if mixer_enabled:
|
||||
try:
|
||||
pygame.mixer.quit()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
mixer_enabled = False
|
||||
|
||||
def enable_mixer():
|
||||
"""
|
||||
This function is called by the video code to enable the
|
||||
pygame mixer.
|
||||
"""
|
||||
|
||||
if not mixer_works:
|
||||
return
|
||||
|
||||
global mixer_enabled
|
||||
|
||||
if not mixer_enabled:
|
||||
try:
|
||||
pygame.mixer.init()
|
||||
except:
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
|
||||
mixer_enabled = True
|
||||
|
||||
+111
-62
@@ -2,6 +2,7 @@
|
||||
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
# import renpy.display.core as core
|
||||
# import renpy.display.layout as layout
|
||||
@@ -23,7 +24,13 @@ def map_event(ev, name):
|
||||
keys = renpy.config.keymap[name]
|
||||
|
||||
if ev.type == MOUSEBUTTONDOWN:
|
||||
if ( "mouse_" + str(ev.button) ) in keys:
|
||||
if ( "mousedown_" + str(ev.button) ) in keys:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
if ev.type == MOUSEBUTTONUP:
|
||||
if ( "mouseup_" + str(ev.button) ) in keys:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
@@ -79,7 +86,7 @@ def skipping(ev):
|
||||
|
||||
return
|
||||
|
||||
class Keymap(renpy.display.layout.Container):
|
||||
class Keymap(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a behavior that maps keys to functions that are called when
|
||||
the key is pressed. The keys are specified by giving the appropriate
|
||||
@@ -87,6 +94,7 @@ class Keymap(renpy.display.layout.Container):
|
||||
"""
|
||||
|
||||
def __init__(self, **keymap):
|
||||
super(Keymap, self).__init__(style='default')
|
||||
self.keymap = keymap
|
||||
|
||||
def event(self, ev, x, y):
|
||||
@@ -96,8 +104,8 @@ class Keymap(renpy.display.layout.Container):
|
||||
action()
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
def render(self, width, height, st):
|
||||
return None
|
||||
# def render(self, width, height, st):
|
||||
# return None
|
||||
|
||||
class KeymouseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
@@ -128,6 +136,26 @@ class KeymouseBehavior(renpy.display.layout.Null):
|
||||
|
||||
return None
|
||||
|
||||
class PauseBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class implementing the Pause behavior, which is to
|
||||
return a value after a certain amount of time has elapsed.
|
||||
"""
|
||||
|
||||
def __init__(self, delay, result=False):
|
||||
super(PauseBehavior, self).__init__()
|
||||
|
||||
self.delay = delay
|
||||
self.result = result
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and ev.duration > self.delay:
|
||||
return self.result
|
||||
|
||||
|
||||
class SayBehavior(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a class that implements the say behavior,
|
||||
@@ -136,18 +164,12 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
mouse button.
|
||||
"""
|
||||
|
||||
def __init__(self, delay=None):
|
||||
def __init__(self):
|
||||
super(SayBehavior, self).__init__()
|
||||
|
||||
self.delay = delay
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
self.delay and ev.duration > self.delay:
|
||||
return False
|
||||
|
||||
if ev.type == renpy.display.core.DISPLAYTIME and \
|
||||
renpy.config.allow_skipping and renpy.config.skipping and \
|
||||
ev.duration > renpy.config.skip_delay / 1000.0:
|
||||
@@ -169,7 +191,7 @@ class SayBehavior(renpy.display.layout.Null):
|
||||
|
||||
class Menu(renpy.display.layout.VBox):
|
||||
|
||||
def __init__(self, menuitems, **properties):
|
||||
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
|
||||
@@ -178,21 +200,27 @@ class Menu(renpy.display.layout.VBox):
|
||||
caption.
|
||||
"""
|
||||
|
||||
super(Menu, self).__init__(**properties)
|
||||
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.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_')
|
||||
# self.selected_style.set_prefix('hover_')
|
||||
# self.unselected_style.set_prefix('idle_')
|
||||
|
||||
for i, (caption, result) in enumerate(menuitems):
|
||||
self.add(renpy.display.text.Text(caption))
|
||||
|
||||
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
|
||||
|
||||
@@ -210,14 +238,13 @@ class Menu(renpy.display.layout.VBox):
|
||||
|
||||
# Captions should stay the default text color.
|
||||
if result is None:
|
||||
child.set_style(self.caption_style)
|
||||
continue
|
||||
|
||||
# Actual choices change color if they are selected or not.
|
||||
if i == self.selected:
|
||||
child.set_style(self.selected_style)
|
||||
child.set_style_prefix('hover_')
|
||||
else:
|
||||
child.set_style(self.unselected_style)
|
||||
child.set_style_prefix('idle_')
|
||||
|
||||
|
||||
def event(self, ev, x, y):
|
||||
@@ -229,25 +256,17 @@ class Menu(renpy.display.layout.VBox):
|
||||
# print x, y
|
||||
|
||||
old_selected = self.selected
|
||||
mouse_select = False
|
||||
|
||||
# Change selection based on mouse position.
|
||||
if ev.type == MOUSEMOTION:
|
||||
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
|
||||
|
||||
# Make selection based on mouse click position.
|
||||
if 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:
|
||||
renpy.sound.play(self.selected_style.activate_sound)
|
||||
return self.results[target]
|
||||
mouse_select = True
|
||||
|
||||
# Change selection based on keypress.
|
||||
if map_event(ev, "menu_keydown"):
|
||||
@@ -271,20 +290,23 @@ class Menu(renpy.display.layout.VBox):
|
||||
self.selected = selected
|
||||
break
|
||||
|
||||
# Make selection based on keypress.
|
||||
if map_event(ev, "menu_keyselect"):
|
||||
renpy.sound.play(self.selected_style.activate_sound)
|
||||
return self.results[self.selected]
|
||||
|
||||
# If the selected item changed, update the display.
|
||||
if self.selected != old_selected:
|
||||
|
||||
self.children[self.selected].set_style(self.selected_style)
|
||||
self.children[old_selected].set_style(self.unselected_style)
|
||||
self.children[self.selected].set_style_prefix("hover_")
|
||||
self.children[old_selected].set_style_prefix("idle_")
|
||||
|
||||
renpy.sound.play(self.selected_style.hover_sound)
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
# renpy.display.render.redraw(self, 0)
|
||||
|
||||
renpy.game.interface.redraw(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
|
||||
|
||||
@@ -297,13 +319,17 @@ class Button(renpy.display.layout.Window):
|
||||
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
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if self.old_hover:
|
||||
if self.activated:
|
||||
self.set_style_prefix('activate_')
|
||||
elif self.old_hover:
|
||||
self.set_style_prefix('hover_')
|
||||
else:
|
||||
self.set_style_prefix('idle_')
|
||||
@@ -322,10 +348,13 @@ class Button(renpy.display.layout.Window):
|
||||
# else:
|
||||
# self.style.set_prefix('idle_')
|
||||
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
# We deactivate on an event.
|
||||
self.activated = False
|
||||
|
||||
inside = False
|
||||
|
||||
width, height = self.window_size
|
||||
@@ -343,12 +372,15 @@ class Button(renpy.display.layout.Window):
|
||||
if self.hovered:
|
||||
self.hovered()
|
||||
|
||||
renpy.sound.play(self.style.hover_sound)
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if map_event(ev, "button_select"):
|
||||
if inside and self.clicked:
|
||||
renpy.sound.play(self.style.activate_sound)
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
|
||||
self.activated = True
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
rv = self.clicked()
|
||||
|
||||
@@ -356,6 +388,7 @@ class Button(renpy.display.layout.Window):
|
||||
return rv
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
|
||||
|
||||
return super(Button, self).event(ev, x, y)
|
||||
@@ -363,10 +396,10 @@ class Button(renpy.display.layout.Window):
|
||||
# Reimplementation of the TextButton widget as a Button and a Text
|
||||
# widget.
|
||||
def TextButton(text, style='button', text_style='button_text',
|
||||
clicked=None):
|
||||
clicked=None, **properties):
|
||||
|
||||
text = renpy.display.text.Text(text, style=text_style)
|
||||
return Button(text, style=style, clicked=clicked)
|
||||
return Button(text, style=style, clicked=clicked, **properties)
|
||||
|
||||
|
||||
|
||||
@@ -398,19 +431,27 @@ class Input(renpy.display.text.Text):
|
||||
"""
|
||||
|
||||
def __init__(self, default, length=None,
|
||||
style='input_text', **properties):
|
||||
super(Input, self).__init__(default + "_", style=style, **properties)
|
||||
style='input_text',
|
||||
allow=None,
|
||||
exclude=None,
|
||||
**properties):
|
||||
|
||||
super(Input, self).__init__(default.replace("{", "{{") + "_", 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 + "_")
|
||||
renpy.game.interface.redraw(0)
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
|
||||
elif map_event(ev, "input_enter"):
|
||||
@@ -421,12 +462,20 @@ class Input(renpy.display.text.Text):
|
||||
return None
|
||||
|
||||
if self.length and len(self.content) >= self.length:
|
||||
return None
|
||||
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()
|
||||
|
||||
self.content += ev.unicode
|
||||
|
||||
self.set_text(self.content + "_")
|
||||
renpy.game.interface.redraw(0)
|
||||
self.set_text(self.content.replace("{", "{{") + "_")
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
|
||||
class Bar(renpy.display.core.Displayable):
|
||||
@@ -461,7 +510,7 @@ class Bar(renpy.display.core.Displayable):
|
||||
if not (0 <= x < self.width and 0 <= y <= self.height):
|
||||
return
|
||||
|
||||
print x, y
|
||||
# print x, y
|
||||
|
||||
lgutter = self.style.left_gutter
|
||||
rgutter = self.style.right_gutter
|
||||
@@ -508,10 +557,10 @@ class Bar(renpy.display.core.Displayable):
|
||||
left_width = barwidth * self.value // self.range
|
||||
right_width = barwidth - left_width
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
lsurf = self.style.left_bar.render(left_width, height, st)
|
||||
rsurf = self.style.right_bar.render(right_width, height, st)
|
||||
lsurf = render(self.style.left_bar, left_width, height, st)
|
||||
rsurf = render(self.style.right_bar, right_width, height, st)
|
||||
|
||||
rv.blit(lsurf, (lgutter, 0))
|
||||
rv.blit(rsurf, (lgutter + left_width, 0))
|
||||
@@ -538,16 +587,16 @@ class Conditional(renpy.display.layout.Container):
|
||||
|
||||
def render(self, width, height, st):
|
||||
if self.state:
|
||||
return self.child.render(width, height, st)
|
||||
return render(self.child, width, height, st)
|
||||
else:
|
||||
return self.null.render(width, height, st)
|
||||
return render(self.null, width, height, st)
|
||||
|
||||
def event(self, ev, x, y):
|
||||
|
||||
state = eval(self.condition, renpy.game.store)
|
||||
|
||||
if state != self.state:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
self.state = state
|
||||
|
||||
|
||||
+317
-213
@@ -2,6 +2,7 @@
|
||||
# window.
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
@@ -34,6 +35,7 @@ class Displayable(renpy.object.Object):
|
||||
|
||||
def __init__(self):
|
||||
self.style = None
|
||||
self.style_prefix = None
|
||||
|
||||
def set_style_prefix(self, prefix):
|
||||
"""
|
||||
@@ -41,9 +43,15 @@ class Displayable(renpy.object.Object):
|
||||
widgets, if any.
|
||||
"""
|
||||
|
||||
if prefix == self.style_prefix:
|
||||
return
|
||||
|
||||
if self.style:
|
||||
self.style.set_prefix(prefix)
|
||||
|
||||
self.style_prefix = prefix
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def parameterize(self, name, parameters):
|
||||
"""
|
||||
Called to parameterize this. By default, we don't take any
|
||||
@@ -62,10 +70,7 @@ class Displayable(renpy.object.Object):
|
||||
and height parameters, which give the largest width and height
|
||||
that this drawable can be drawn to without overflowing some
|
||||
bounding box. It's also given two times. It returns a Surface
|
||||
that is the current image of this drawable. If the image will
|
||||
be need to be redrawn at some time in the future, call
|
||||
game.interface.redraw with the time with the delay until the
|
||||
next redraw.
|
||||
that is the current image of this drawable.
|
||||
|
||||
@param shown_time: The time since we first started showing this
|
||||
drawable, a float in seconds.
|
||||
@@ -171,8 +176,7 @@ class Displayable(renpy.object.Object):
|
||||
dest.blit(surf, (xoff, yoff))
|
||||
|
||||
return xoff, yoff
|
||||
|
||||
|
||||
|
||||
|
||||
class SceneLists(object):
|
||||
"""
|
||||
@@ -194,19 +198,26 @@ class SceneLists(object):
|
||||
"""
|
||||
|
||||
def __init__(self, oldsl=None):
|
||||
|
||||
|
||||
self.layers = { }
|
||||
|
||||
if oldsl:
|
||||
self.master = oldsl.master[:]
|
||||
|
||||
for i in renpy.config.layers:
|
||||
self.layers[i] = oldsl.layers[i][:]
|
||||
|
||||
self.replace_transient()
|
||||
self.overlay = oldsl.overlay[:]
|
||||
|
||||
self.music = oldsl.music
|
||||
self.sticky_positions = oldsl.sticky_positions.copy()
|
||||
|
||||
self.movie = oldsl.movie
|
||||
|
||||
else:
|
||||
self.master = [ ]
|
||||
self.transient = [ ]
|
||||
self.overlay = [ ]
|
||||
for i in renpy.config.layers:
|
||||
self.layers[i] = [ ]
|
||||
|
||||
self.music = None
|
||||
self.movie = None
|
||||
self.sticky_positions = { }
|
||||
|
||||
def rollback_copy(self):
|
||||
@@ -215,11 +226,16 @@ class SceneLists(object):
|
||||
which is a 2-level copy.
|
||||
"""
|
||||
|
||||
rv = SceneLists()
|
||||
rv.master = self.master[:]
|
||||
rv.transient = self.transient[:]
|
||||
rv = SceneLists(self)
|
||||
|
||||
rv.music = self.music
|
||||
for i in renpy.config.overlay_layers:
|
||||
rv.layers[i] = [ ]
|
||||
|
||||
# rv.master = self.master[:]
|
||||
# rv.transient = self.transient[:]
|
||||
|
||||
# rv.music = self.music
|
||||
# rv.movie = self.movie
|
||||
|
||||
return rv
|
||||
|
||||
@@ -232,27 +248,28 @@ class SceneLists(object):
|
||||
elements.
|
||||
"""
|
||||
|
||||
self.transient = [ ]
|
||||
for i in renpy.config.transient_layers:
|
||||
self.layers[i] = [ ]
|
||||
|
||||
def add(self, listname, thing, key=None):
|
||||
def add(self, layer, thing, key=None):
|
||||
"""
|
||||
This is called to add something to a display list. Listname is
|
||||
the name of the displaylist that we need to add the thing to,
|
||||
This is called to add something to a layer. Layer is
|
||||
the name of the layer that we need to add the thing to,
|
||||
one of 'master' or 'transient'. Key is an optional key.
|
||||
|
||||
If key is provided, and there exists something in the selected
|
||||
displaylist with the given key, that entry from the displaylist
|
||||
layer with the given key, that entry from the layer
|
||||
is replaced with the supplied displayable, preserving the
|
||||
time it was first displayed.
|
||||
|
||||
Otherwise, the displayable is added to the end of the list, and
|
||||
Otherwise, the displayable is added to the end of the layer, and
|
||||
the time it was first displayed is set to the current time.
|
||||
"""
|
||||
|
||||
l = getattr(self, listname, None)
|
||||
if layer not in self.layers:
|
||||
raise Exception("Trying to add something to non-existent layer '%s'." % layer)
|
||||
|
||||
if l is None:
|
||||
raise Exception("Trying to add something to non-existent display list '%s'." % listname)
|
||||
l = self.layers[layer]
|
||||
|
||||
if key is not None:
|
||||
for index, (k, t, d) in enumerate(l):
|
||||
@@ -267,71 +284,33 @@ class SceneLists(object):
|
||||
|
||||
l.append((key, time.time(), thing))
|
||||
|
||||
def remove(self, listname, thing):
|
||||
def remove(self, layer, thing):
|
||||
"""
|
||||
This is either a key or a displayable. This iterates through the
|
||||
named display list, searching for entries matching the thing.
|
||||
named layer, searching for entries matching the thing.
|
||||
When they are found, they are removed from the displaylist.
|
||||
|
||||
It's not an error to remove something that isn't in the list in
|
||||
It's not an error to remove something that isn't in the layer in
|
||||
the first place.
|
||||
"""
|
||||
|
||||
l = getattr(self, listname)
|
||||
if layer not in self.layers:
|
||||
raise Exception("Trying to remove something from non-existent layer '%s'." % layer)
|
||||
|
||||
l = self.layers[layer]
|
||||
l = [ (k, t, d) for k, t, d in l if k != thing if d is not thing ]
|
||||
|
||||
setattr(self, listname, l)
|
||||
self.layers[layer] = l
|
||||
|
||||
def clear(self, listname):
|
||||
def clear(self, layer):
|
||||
"""
|
||||
Resets the given listname to either the empty list, or to the
|
||||
contents of the empty scenelist if reset_to_empty is True.
|
||||
|
||||
@param: The name of the list to clear. One of the strings
|
||||
'master' or 'transient'.
|
||||
Clears the named layer, making it empty.
|
||||
"""
|
||||
|
||||
l = getattr(self, listname, None)
|
||||
|
||||
if l is None:
|
||||
raise Exception("Trying to clear non-existent display list '%s'." % listname)
|
||||
if layer not in self.layers is None:
|
||||
raise Exception("Trying to clear non-existent layer '%s'." % layer)
|
||||
|
||||
nl = [ ]
|
||||
|
||||
setattr(self, listname, nl)
|
||||
|
||||
def render_scene_list(sl, width, height):
|
||||
"""
|
||||
This renders the scene list sl, and returns a rendered
|
||||
renpy Surface containing the rendered scene list.
|
||||
|
||||
@returns: The surface, and the offset of each member of the
|
||||
list relative to the surface.
|
||||
"""
|
||||
|
||||
t = time.time()
|
||||
|
||||
surftree = renpy.display.surface.Surface(width, height)
|
||||
offsets = [ ]
|
||||
|
||||
if renpy.config.background:
|
||||
surftree.fill(renpy.config.background)
|
||||
|
||||
for key, base_start_time, d in sl:
|
||||
|
||||
surf = d.render(width, height, t - base_start_time)
|
||||
|
||||
if not surf:
|
||||
offsets.append((0, 0))
|
||||
continue
|
||||
|
||||
# Place the surface.
|
||||
offset = d.place(surftree, 0, 0, width, height, surf)
|
||||
|
||||
offsets.append(offset)
|
||||
|
||||
return surftree, offsets
|
||||
self.layers[layer] = [ ]
|
||||
|
||||
|
||||
class Display(object):
|
||||
@@ -353,13 +332,20 @@ class Display(object):
|
||||
|
||||
@ivar mouse_location: The mouse location the last time it was
|
||||
drawn, or None if it wasn't drawn the last time around.
|
||||
|
||||
@ivar full_redraw: Force a full redraw.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
|
||||
renpy.sound.init()
|
||||
|
||||
# Ensure that we kill off the movie when changing screen res.
|
||||
renpy.display.video.movie_stop(clear=False)
|
||||
|
||||
renpy.display.audio.pre_init()
|
||||
pygame.init()
|
||||
renpy.display.audio.init()
|
||||
|
||||
self.fullscreen = renpy.game.preferences.fullscreen
|
||||
fsflag = 0
|
||||
@@ -398,8 +384,11 @@ class Display(object):
|
||||
pygame.mouse.set_visible(True)
|
||||
|
||||
self.mouse_location = None
|
||||
self.suppress_mouse = False
|
||||
|
||||
def draw_mouse(self):
|
||||
self.full_redraw = True
|
||||
|
||||
def draw_mouse(self, show_mouse=True):
|
||||
"""
|
||||
This draws the mouse to the screen, if necessary. It uses the
|
||||
buffer to minimize the amount of the screen that needs to be
|
||||
@@ -409,34 +398,35 @@ class Display(object):
|
||||
if not self.mouse:
|
||||
return
|
||||
|
||||
if self.suppress_mouse:
|
||||
return
|
||||
|
||||
mw, mh = self.mouse.get_size()
|
||||
pos = pygame.mouse.get_pos()
|
||||
|
||||
if not pygame.mouse.get_focused():
|
||||
pos = None
|
||||
|
||||
flip = False
|
||||
if not show_mouse:
|
||||
pos = None
|
||||
|
||||
updates = [ ]
|
||||
|
||||
if self.mouse_location and self.mouse_location != pos:
|
||||
ox, oy = self.mouse_location
|
||||
self.window.blit(self.buffer, (ox, oy), (ox, oy, mw, mh))
|
||||
flip = True
|
||||
updates.append((ox, oy, mw, mh))
|
||||
|
||||
if pos and (pos != self.mouse_location):
|
||||
self.window.blit(self.mouse, pos)
|
||||
flip = True
|
||||
updates.append(pos + (mw, mh))
|
||||
|
||||
self.mouse_location = pos
|
||||
|
||||
|
||||
if flip:
|
||||
pygame.display.flip()
|
||||
|
||||
|
||||
pygame.display.update(updates)
|
||||
|
||||
|
||||
def show(self, transient):
|
||||
def show(self, root_widget, suppress_blit):
|
||||
"""
|
||||
Draws the current transient screen list to the screen.
|
||||
|
||||
@@ -444,23 +434,34 @@ class Display(object):
|
||||
relative to the screen.
|
||||
"""
|
||||
|
||||
surftree, rv = render_scene_list(transient,
|
||||
renpy.config.screen_width,
|
||||
renpy.config.screen_height)
|
||||
surftree = renpy.display.render.render_screen(
|
||||
root_widget,
|
||||
renpy.config.screen_width,
|
||||
renpy.config.screen_height,
|
||||
0)
|
||||
|
||||
if not suppress_blit:
|
||||
|
||||
if self.mouse:
|
||||
self.draw_mouse(False)
|
||||
|
||||
damage = renpy.display.render.screen_blit(surftree, self.full_redraw)
|
||||
self.full_redraw = False
|
||||
|
||||
if self.mouse:
|
||||
if damage:
|
||||
self.buffer.blit(self.window, damage, damage)
|
||||
self.draw_mouse(True)
|
||||
|
||||
if damage:
|
||||
pygame.display.update(damage)
|
||||
|
||||
else:
|
||||
self.full_redraw = True
|
||||
|
||||
self.suppress_mouse = suppress_blit
|
||||
|
||||
# self.window.set_clip((0, 0, 800, 300))
|
||||
|
||||
surftree.blit_to(self.window, 0, 0)
|
||||
|
||||
if self.mouse:
|
||||
self.buffer.blit(self.window, (0, 0))
|
||||
# We don't need to undraw the mouse.
|
||||
self.mouse_location = None
|
||||
|
||||
pygame.display.flip()
|
||||
|
||||
return rv
|
||||
|
||||
def save_screenshot(self, filename):
|
||||
"""
|
||||
Saves a full-size screenshot in the given filename.
|
||||
@@ -491,38 +492,44 @@ class Interface(object):
|
||||
|
||||
@ivar display: The display that we used to display the screen.
|
||||
|
||||
@ivar needs_redraw: True if we need a redraw now.
|
||||
|
||||
@ivar profile_time: The time of the last profiling.
|
||||
|
||||
@ivar screenshot: A screenshot, or None if no screenshot has been
|
||||
taken.
|
||||
|
||||
@ivar old_scene: The last thing that was displayed to the screen, not
|
||||
counting overlays and things like that.
|
||||
counting underlays.
|
||||
|
||||
@ivar transition: If not None, the transition to be applied for the
|
||||
next interaction.
|
||||
@ivar transition: A map from layer name to the transition that will
|
||||
be applied the next time interact restarts.
|
||||
|
||||
@ivar supress_transition: If True, then the next transition will not
|
||||
@ivar suppress_transition: If True, then the next transition will not
|
||||
happen.
|
||||
|
||||
@ivar quick_quit: If true, a click on the delete button will
|
||||
cause an immediate quit.
|
||||
|
||||
@ivar force_redraw: If True, a redraw is forced.
|
||||
|
||||
@ivar restart_interaction: If True, the current interaction will
|
||||
be restarted.
|
||||
|
||||
@ivar pushed_event: If not None, an event that was pushed back
|
||||
onto the stack.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.display = Display()
|
||||
self.needs_redraw = False
|
||||
self.profile_time = time.time()
|
||||
self.screenshot = None
|
||||
self.redraw_time = 0.0
|
||||
self.old_scene = [ ]
|
||||
self.transition = None
|
||||
self.supress_transition = False
|
||||
self.old_scene = None
|
||||
self.transition = { }
|
||||
self.suppress_transition = False
|
||||
self.quick_quit = False
|
||||
self.force_redraw = False
|
||||
self.restart_interaction = False
|
||||
self.pushed_event = None
|
||||
|
||||
def take_screenshot(self, scale):
|
||||
"""
|
||||
@@ -549,37 +556,48 @@ class Interface(object):
|
||||
|
||||
self.screenshot = None
|
||||
|
||||
|
||||
def redraw(self, delay=0.0):
|
||||
"""
|
||||
Called to indicate that the screen has changed in some way, and
|
||||
needs to be redrawn.
|
||||
|
||||
@param delay: The time from now at which the redraw needs to
|
||||
be performed, in fractional seconds.
|
||||
"""
|
||||
|
||||
if delay == 0.0:
|
||||
self.needs_redraw = True
|
||||
else:
|
||||
self.redraw_time = min(self.redraw_time, time.time() + delay)
|
||||
|
||||
def with_none(self):
|
||||
"""
|
||||
Implements the with None command, which sets the scene we will
|
||||
be transitioning from.
|
||||
"""
|
||||
|
||||
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
self.old_scene = scene_lists.master[:]
|
||||
|
||||
def set_transition(self, transition):
|
||||
# We don't want the overlay to be clear here.
|
||||
|
||||
if not renpy.config.overlay_during_wait:
|
||||
for i in renpy.config.overlay_layers:
|
||||
scene_lists.clear(i)
|
||||
|
||||
self.old_scene = self.compute_scene(scene_lists)
|
||||
|
||||
|
||||
# self.old_scene.append_scene_list(scene_lists.master)
|
||||
|
||||
def set_transition(self, transition, layer=None):
|
||||
"""
|
||||
Sets the transition that will be performed as part of the next
|
||||
interaction.
|
||||
"""
|
||||
|
||||
self.transition = transition
|
||||
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):
|
||||
"""
|
||||
@@ -587,22 +605,78 @@ class Interface(object):
|
||||
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.image.cache.needs_preload():
|
||||
ev = pygame.event.poll()
|
||||
if ev.type != NOEVENT:
|
||||
return ev
|
||||
|
||||
renpy.display.image.cache.preload()
|
||||
|
||||
return pygame.event.wait()
|
||||
|
||||
def interact(self, transient=None, show_mouse=True,
|
||||
|
||||
def compute_scene(self, scene_lists):
|
||||
"""
|
||||
This converts scene lists into a dictionary mapping layer
|
||||
name to a Fixed containing that layer. None is mapped
|
||||
to a fixed containing all of the layers.
|
||||
"""
|
||||
|
||||
rv = { }
|
||||
|
||||
root = renpy.display.layout.Fixed()
|
||||
|
||||
for layer in renpy.config.layers:
|
||||
f = renpy.display.layout.Fixed()
|
||||
f.append_scene_list(scene_lists.layers[layer])
|
||||
|
||||
rv[layer] = f
|
||||
root.add(f)
|
||||
|
||||
rv[None] = root
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def interact(self, **kwargs):
|
||||
"""
|
||||
This handles an interaction, restarting it if necessary. All of the
|
||||
keyword arguments are passed off to interact_core.
|
||||
"""
|
||||
|
||||
# These things can be done once per interaction.
|
||||
|
||||
repeat = True
|
||||
|
||||
while repeat:
|
||||
repeat, rv = self.interact_core(**kwargs)
|
||||
|
||||
# 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,
|
||||
show_mouse=True,
|
||||
trans_pause=False,
|
||||
suppress_overlay=False,
|
||||
suppress_underlay=False,
|
||||
):
|
||||
|
||||
"""
|
||||
This handles one cycle of displaying an image to the user,
|
||||
and then responding to user input.
|
||||
|
||||
@param transient: If given, a replacement list of transient
|
||||
things to show.
|
||||
|
||||
@param show_mouse: Should the mouse be shown during this
|
||||
interaction? Only advisory.
|
||||
interaction? Only advisory, and usually doesn't work.
|
||||
|
||||
@param trans_pause: If given, we must have a transition. Should we
|
||||
add a pause behavior during the transition?
|
||||
@@ -611,22 +685,23 @@ class Interface(object):
|
||||
@param suppress_underlay: This suppresses the display of the underlay.
|
||||
"""
|
||||
|
||||
self.suppress_transition |= renpy.config.skipping
|
||||
|
||||
## Safety condition, prevents deadlocks.
|
||||
if trans_pause:
|
||||
if not self.transition:
|
||||
return None
|
||||
if self.supress_transition:
|
||||
return None
|
||||
return False, None
|
||||
if self.suppress_transition:
|
||||
return False, None
|
||||
|
||||
# We just restarted.
|
||||
self.restart_interaction = False
|
||||
|
||||
## Expensive things we want to do before we pick the start_time.
|
||||
# frames = 0
|
||||
|
||||
# Tick time forward.
|
||||
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)
|
||||
|
||||
@@ -642,69 +717,85 @@ class Interface(object):
|
||||
start_time = time.time()
|
||||
scene_lists = renpy.game.context().scene_lists
|
||||
|
||||
# Compute the overlay, by calling the overlay functions.
|
||||
overlay = [ ]
|
||||
|
||||
# Figure out what the overlay layer should look like.
|
||||
for i in renpy.config.overlay_layers:
|
||||
scene_lists.clear(i)
|
||||
|
||||
renpy.ui.layer("overlay")
|
||||
|
||||
if not suppress_overlay:
|
||||
for i in renpy.config.overlay_functions:
|
||||
i()
|
||||
|
||||
overlaid = i()
|
||||
|
||||
if overlaid:
|
||||
for j in overlaid:
|
||||
overlay.append((None, 0, j))
|
||||
renpy.ui.close()
|
||||
|
||||
# Figure out the underlay layer.
|
||||
if not suppress_underlay:
|
||||
underlay = [ ( None, 0, i) for i in renpy.config.underlay ]
|
||||
else:
|
||||
underlay = [ ]
|
||||
|
||||
# Set up the transient scene list.
|
||||
if transient:
|
||||
transient = scene_lists.transient + [ (None, start_time, i) for i in transient ]
|
||||
else:
|
||||
transient = scene_lists.transient
|
||||
# The root widget of everything that is displayed on the screen.
|
||||
root_widget = renpy.display.layout.Fixed()
|
||||
root_widget.append_scene_list(underlay)
|
||||
|
||||
|
||||
# Figure out the display list.
|
||||
current_scene = scene_lists.master + transient + overlay
|
||||
# Figure out the scene. (All of the layers, and the root.)
|
||||
scene = self.compute_scene(scene_lists)
|
||||
|
||||
if self.transition and not self.supress_transition:
|
||||
trans = self.transition(self.old_scene, current_scene)
|
||||
# The root widget of all of the layers.
|
||||
layers_root = renpy.display.layout.Fixed()
|
||||
|
||||
transition_scene = [ (None, start_time, trans) ]
|
||||
# Add layers (perhaps with transitions) to the layers root.
|
||||
for layer in renpy.config.layers:
|
||||
if layer in self.transition and self.old_scene and not self.suppress_transition:
|
||||
|
||||
trans = self.transition[layer](old_widget=self.old_scene[layer],
|
||||
new_widget=scene[layer])
|
||||
layers_root.add(trans, start_time)
|
||||
else:
|
||||
layers_root.add(scene[layer], start_time)
|
||||
|
||||
# Add layers_root to root_widget, perhaps through a transition.
|
||||
if None in self.transition and self.old_scene and not self.suppress_transition:
|
||||
trans = self.transition[None](old_widget=self.old_scene[None],
|
||||
new_widget=layers_root)
|
||||
|
||||
root_widget.add(trans, start_time)
|
||||
|
||||
if trans_pause:
|
||||
sb = renpy.display.behavior.SayBehavior(delay=trans.delay)
|
||||
transition_scene.append((None, start_time, sb))
|
||||
sb = renpy.display.behavior.SayBehavior()
|
||||
root_widget.add(sb, start_time)
|
||||
|
||||
pb = renpy.display.behavior.PauseBehavior(trans.delay)
|
||||
root_widget.add(pb, start_time)
|
||||
|
||||
else:
|
||||
transition_scene = current_scene
|
||||
root_widget.add(layers_root, start_time)
|
||||
|
||||
self.transition = None
|
||||
self.supress_transition = False
|
||||
|
||||
# The list of things to be displayed.
|
||||
display_list = underlay + transition_scene
|
||||
# Now, update various things regarding scenes and transitions,
|
||||
# so we are ready for a new interaction or a restart.
|
||||
self.old_scene = scene
|
||||
self.transition = { }
|
||||
self.suppress_transition = False
|
||||
|
||||
|
||||
# This list of things recieving events.
|
||||
event_list = display_list[:]
|
||||
# Okay, from here on we now have a single root widget (root_widget),
|
||||
# which we will try to show to the user.
|
||||
|
||||
# Compute the reversed list, which is in the right order
|
||||
# for handling events on.
|
||||
event_list.reverse()
|
||||
|
||||
# Redraw the screen during every interaction.
|
||||
self.needs_redraw = True
|
||||
|
||||
# This list of offsets will be filled in before we need
|
||||
# it. It's kept in reverse order of the transient list
|
||||
# (The same order as rev_transient.)
|
||||
offsets = [ ]
|
||||
# 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
|
||||
|
||||
rv = None
|
||||
|
||||
# This try block is used to force cleanup even on termination
|
||||
@@ -713,42 +804,55 @@ class Interface(object):
|
||||
|
||||
while rv is None:
|
||||
|
||||
# Check for a change in fullscreen preference.
|
||||
if self.display.fullscreen != renpy.game.preferences.fullscreen:
|
||||
self.display = Display()
|
||||
self.needs_redraw = True
|
||||
needs_redraw = True
|
||||
|
||||
if self.needs_redraw:
|
||||
self.needs_redraw = False
|
||||
# Check for a forced redraw.
|
||||
if self.force_redraw:
|
||||
needs_redraw = True
|
||||
self.force_redraw = False
|
||||
|
||||
# Redraw the screen.
|
||||
if needs_redraw:
|
||||
needs_redraw = False
|
||||
|
||||
# If we have a movie, start showing it.
|
||||
suppress_blit = renpy.display.video.interact()
|
||||
|
||||
# Draw the screen.
|
||||
draw_start = time.time()
|
||||
self.redraw_time = draw_start + 365.25 * 86400.0
|
||||
|
||||
offsets = self.display.show(display_list)
|
||||
offsets.reverse()
|
||||
self.display.show(root_widget, suppress_blit)
|
||||
|
||||
renpy.config.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 between event and display." % (new_time - self.profile_time)
|
||||
print "Profile: %f seconds to complete event." % (new_time - self.profile_time)
|
||||
|
||||
# Draw the mouse, if it needs drawing.
|
||||
if show_mouse:
|
||||
self.display.draw_mouse()
|
||||
|
||||
|
||||
# Determine if we need a redraw.
|
||||
needs_redraw = renpy.display.render.process_redraws()
|
||||
|
||||
# If we need to redraw again, do it if we don't have an
|
||||
# event going on.
|
||||
if self.needs_redraw and not pygame.event.peek():
|
||||
if needs_redraw and not self.event_peek():
|
||||
continue
|
||||
|
||||
# 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()
|
||||
# Predict images, if we haven't done so already.
|
||||
if not did_prediction and not self.event_peek():
|
||||
renpy.game.context().predict(renpy.display.image.cache.preload_image)
|
||||
root_widget.predict(renpy.display.image.cache.preload_image)
|
||||
did_prediction = True
|
||||
|
||||
try:
|
||||
# ev = pygame.event.wait()
|
||||
ev = self.event_wait()
|
||||
self.profile_time = time.time()
|
||||
|
||||
@@ -762,7 +866,10 @@ class Interface(object):
|
||||
duration=(time.time() - start_time))
|
||||
|
||||
# Update the playing music, if necessary.
|
||||
renpy.music.restore()
|
||||
|
||||
# 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)
|
||||
@@ -783,39 +890,36 @@ class Interface(object):
|
||||
if len(evs):
|
||||
ev = evs[-1]
|
||||
|
||||
|
||||
# x, y = getattr(ev, 'pos', (0, 0))
|
||||
|
||||
x, y = pygame.mouse.get_pos()
|
||||
|
||||
for (k, t, d), (xo, yo) in zip(event_list, offsets):
|
||||
rv = d.event(ev, x - xo, y - yo)
|
||||
rv = root_widget.event(ev, x, y)
|
||||
|
||||
if rv is not None:
|
||||
break
|
||||
if rv is not None:
|
||||
break
|
||||
|
||||
except IgnoreEvent:
|
||||
pass
|
||||
|
||||
if time.time() > self.redraw_time:
|
||||
self.needs_redraw = True
|
||||
# Check again after handling the event.
|
||||
needs_redraw |= renpy.display.render.process_redraws()
|
||||
|
||||
if self.restart_interaction:
|
||||
return True, None
|
||||
|
||||
# But wait, there's more! The finally block runs some cleanup
|
||||
# after this.
|
||||
return rv
|
||||
return False, rv
|
||||
|
||||
finally:
|
||||
|
||||
# pygame.time.set_timer(KEYREPEATEVENT, 0)
|
||||
pygame.time.set_timer(DISPLAYTIME, 0)
|
||||
scene_lists.replace_transient()
|
||||
|
||||
# Clear up the transitions.
|
||||
self.old_scene = current_scene
|
||||
self.transition = None
|
||||
self.supress_transition = False
|
||||
# Restart the old interaction, which also causes a
|
||||
# redraw if needed.
|
||||
self.restart_interaction = True
|
||||
|
||||
# Redraw the old scene, if any.
|
||||
self.redraw(0)
|
||||
|
||||
# print "It took", frames, "frames."
|
||||
|
||||
|
||||
+65
-22
@@ -1,4 +1,6 @@
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
@@ -47,11 +49,19 @@ class ImageCache(object):
|
||||
layer = self.load_image(i)
|
||||
rv.blit(layer, (0, 0))
|
||||
|
||||
renpy.display.render.mutated_surface(rv)
|
||||
|
||||
return rv
|
||||
|
||||
im = pygame.image.load(renpy.loader.load(fn), fn)
|
||||
im = im.convert_alpha()
|
||||
|
||||
if im.get_flags() & SRCALPHA:
|
||||
im = im.convert_alpha()
|
||||
else:
|
||||
im = im.convert()
|
||||
|
||||
renpy.display.render.mutated_surface(im)
|
||||
|
||||
return im
|
||||
|
||||
|
||||
@@ -67,7 +77,7 @@ class ImageCache(object):
|
||||
|
||||
# iw, ih = im.get_size()
|
||||
|
||||
# surf = renpy.display.surface.Surface(iw, ih)
|
||||
# surf = renpy.display.render.Render(iw, ih)
|
||||
# surf.blit(im, (0, 0))
|
||||
|
||||
im = self.really_load_image(fn)
|
||||
@@ -82,6 +92,7 @@ class ImageCache(object):
|
||||
# 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:
|
||||
@@ -90,7 +101,6 @@ class ImageCache(object):
|
||||
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.)
|
||||
@@ -176,6 +186,8 @@ class Image(renpy.display.core.Displayable):
|
||||
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)
|
||||
@@ -183,7 +195,7 @@ class Image(renpy.display.core.Displayable):
|
||||
def render(self, w, h, st):
|
||||
im = cache.load_image(self.filename)
|
||||
w, h = im.get_size()
|
||||
rv = renpy.display.surface.Surface(w, h)
|
||||
rv = renpy.display.render.Render(w, h)
|
||||
rv.blit(im, (0, 0))
|
||||
return rv
|
||||
|
||||
@@ -200,11 +212,17 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
|
||||
def __init__(self, file, hint=None, scale=None, style='image_placement',
|
||||
**properties):
|
||||
|
||||
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):
|
||||
@@ -212,14 +230,14 @@ class UncachedImage(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, w, h, st):
|
||||
sw, sh = self.surf.get_size()
|
||||
rv = renpy.display.surface.Surface(sw, sh)
|
||||
rv = renpy.display.render.Render(sw, sh)
|
||||
rv.blit(self.surf, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
# Should never be called, but what the hey?
|
||||
def predict(self, callback):
|
||||
callback(self.filename)
|
||||
return None
|
||||
|
||||
class ImageReference(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -236,6 +254,8 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
nosave = [ 'target' ]
|
||||
|
||||
def __init__(self, name):
|
||||
super(ImageReference, self).__init__()
|
||||
|
||||
self.name = name
|
||||
|
||||
def find_target(self):
|
||||
@@ -276,17 +296,22 @@ class ImageReference(renpy.display.core.Displayable):
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
if not hasattr(self, 'target'):
|
||||
self.find_target()
|
||||
|
||||
return self.target.render(width, height, st)
|
||||
return render(self.target, width, height, st)
|
||||
|
||||
def get_placement(self):
|
||||
if not hasattr(self, 'target'):
|
||||
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):
|
||||
@@ -301,11 +326,12 @@ class Solid(renpy.display.core.Displayable):
|
||||
@param color: An RGBA tuple, giving the color that the display will be filled with.
|
||||
"""
|
||||
|
||||
super(Solid, self).__init__()
|
||||
self.color = color
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.fill(self.color)
|
||||
|
||||
return rv
|
||||
@@ -339,19 +365,16 @@ class Frame(renpy.display.core.Displayable):
|
||||
at. We detect this and avoid scaling if possible.
|
||||
"""
|
||||
|
||||
super(Frame, self).__init__()
|
||||
|
||||
self.filename = filename
|
||||
self.xborder = xborder
|
||||
self.yborder = yborder
|
||||
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
|
||||
if hasattr(self, 'cache'):
|
||||
if self.cache.get_size() == (width, height):
|
||||
return self.cache
|
||||
|
||||
dest = renpy.display.surface.Surface(width, height)
|
||||
dest = renpy.display.render.Render(width, height)
|
||||
dw, dh = width, height
|
||||
|
||||
source = cache.load_image(self.filename)
|
||||
@@ -432,8 +455,10 @@ class Frame(renpy.display.core.Displayable):
|
||||
draw(-xb, 0, -yb, 0)
|
||||
|
||||
# And, finish up.
|
||||
self.cache = dest
|
||||
return dest
|
||||
|
||||
def predict(self, callback):
|
||||
callback(self.filename)
|
||||
|
||||
class Animation(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -451,6 +476,8 @@ class Animation(renpy.display.core.Displayable):
|
||||
animation will restart after the final delay time.
|
||||
"""
|
||||
|
||||
super(Animation, self).__init__()
|
||||
|
||||
self.images = [ ]
|
||||
self.delays = [ ]
|
||||
|
||||
@@ -470,8 +497,15 @@ class Animation(renpy.display.core.Displayable):
|
||||
|
||||
for image, delay in zip(self.images, self.delays):
|
||||
if t < delay:
|
||||
renpy.game.interface.redraw(delay - t)
|
||||
return cache.load_image(image)
|
||||
renpy.display.render.redraw(self, delay - t)
|
||||
|
||||
im = cache.load_image(image)
|
||||
width, height = im.get_size()
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(im, (0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
else:
|
||||
t = t - delay
|
||||
|
||||
@@ -491,6 +525,8 @@ class ImageMap(renpy.display.core.Displayable):
|
||||
def __init__(self, ground, selected, hotspots, unselected=None,
|
||||
style='imagemap', **properties):
|
||||
|
||||
super(ImageMap, self).__init__()
|
||||
|
||||
self.ground = ground
|
||||
self.selected = selected
|
||||
self.hotspots = hotspots
|
||||
@@ -501,6 +537,7 @@ class ImageMap(renpy.display.core.Displayable):
|
||||
self.unselected = unselected
|
||||
|
||||
self.active = None
|
||||
self.last_active = None
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
@@ -520,7 +557,7 @@ class ImageMap(renpy.display.core.Displayable):
|
||||
unselected = cache.load_image(self.unselected)
|
||||
|
||||
width, height = ground.get_size()
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
rv.blit(ground, (0, 0))
|
||||
|
||||
for i, hotspot in enumerate(self.hotspots):
|
||||
@@ -533,6 +570,8 @@ class ImageMap(renpy.display.core.Displayable):
|
||||
source = unselected
|
||||
|
||||
subsurface = source.subsurface((x0, y0, x1-x0, y1-y0))
|
||||
renpy.display.render.mutated_surface(subsurface)
|
||||
|
||||
rv.blit(subsurface, (x0, y0))
|
||||
|
||||
return rv
|
||||
@@ -551,17 +590,17 @@ class ImageMap(renpy.display.core.Displayable):
|
||||
|
||||
if old_active != active:
|
||||
self.active = active
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
if active is not None:
|
||||
renpy.sound.play(self.style.hover_sound)
|
||||
renpy.display.audio.play(self.style.hover_sound)
|
||||
|
||||
|
||||
if active is None:
|
||||
return None
|
||||
|
||||
if renpy.display.behavior.map_event(ev, "imagemap_select"):
|
||||
renpy.sound.play(self.style.activate_sound)
|
||||
renpy.display.audio.play(self.style.activate_sound)
|
||||
return result
|
||||
|
||||
return None
|
||||
@@ -583,6 +622,10 @@ class ImageButton(renpy.display.behavior.Button):
|
||||
hovered=hovered)
|
||||
|
||||
|
||||
def predict(self, callback):
|
||||
self.idle_image.predict(callback)
|
||||
self.hover_image.predict(callback)
|
||||
|
||||
def set_hover(self, hover):
|
||||
super(ImageButton, self).set_hover(hover)
|
||||
|
||||
|
||||
+198
-61
@@ -5,6 +5,19 @@ import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
import time
|
||||
|
||||
def scale(num, base):
|
||||
"""
|
||||
If num is a float, multiplies it by base and returns that. Otherwise,
|
||||
returns num unchanged.
|
||||
"""
|
||||
|
||||
if isinstance(num, float):
|
||||
return num * base
|
||||
else:
|
||||
return num
|
||||
|
||||
class Null(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -20,8 +33,11 @@ class Null(renpy.display.core.Displayable):
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
return renpy.display.surface.Surface(self.width, self.height)
|
||||
return renpy.display.render.Render(self.width, self.height)
|
||||
|
||||
|
||||
class Container(renpy.display.core.Displayable):
|
||||
@@ -73,7 +89,7 @@ class Container(renpy.display.core.Displayable):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
rv = self.child.render(width, height, st)
|
||||
rv = render(self.child, width, height, st)
|
||||
self.offsets = [ (0, 0) ]
|
||||
self.sizes = [ rv.get_size() ]
|
||||
|
||||
@@ -83,7 +99,10 @@ class Container(renpy.display.core.Displayable):
|
||||
return self.child.get_placement()
|
||||
|
||||
def event(self, ev, x, y):
|
||||
for i, (xo, yo) in zip(self.children, self.offsets):
|
||||
children_offsets = zip(self.children, self.offsets)
|
||||
children_offsets.reverse()
|
||||
|
||||
for i, (xo, yo) in children_offsets:
|
||||
rv = i.event(ev, x - xo, y - yo)
|
||||
if rv is not None:
|
||||
return rv
|
||||
@@ -116,6 +135,67 @@ class Container(renpy.display.core.Displayable):
|
||||
for i in self.children:
|
||||
i.predict(callback)
|
||||
|
||||
class Fixed(Container):
|
||||
"""
|
||||
A container that lays out each of its children at fixed
|
||||
coordinates determined by the position style of the child. Each
|
||||
widget is given the whole area of this widget, and then placed
|
||||
within that area based on its position style.
|
||||
|
||||
The result of this layout is the size of the entire area allocated
|
||||
to it. So it's probably only viable for laying out a root window.
|
||||
|
||||
Fixed is used by the display core to render scene lists, and to
|
||||
pass them off to transitions.
|
||||
"""
|
||||
|
||||
def __init__(self, style='default', **properties):
|
||||
super(Fixed, self).__init__()
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
self.times = [ ]
|
||||
|
||||
def add(self, widget, time=None):
|
||||
super(Fixed, self).add(widget)
|
||||
self.times.append(time)
|
||||
|
||||
def append_scene_list(self, l):
|
||||
for tag, time, d in l:
|
||||
self.add(d, time)
|
||||
|
||||
def get_widget_time_list(self):
|
||||
return zip(self.children, self.times)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
self.offsets = [ ]
|
||||
self.sizes = [ ]
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
t = time.time()
|
||||
|
||||
for child, start in zip(self.children, self.times):
|
||||
|
||||
if start:
|
||||
newst = t - start
|
||||
else:
|
||||
newst = st
|
||||
|
||||
surf = render(child, width, height, newst)
|
||||
|
||||
if surf:
|
||||
self.sizes.append(surf.get_size())
|
||||
offset = child.place(rv, 0, 0, width, height, surf)
|
||||
self.offsets.append(offset)
|
||||
else:
|
||||
self.sizes.append((0, 0))
|
||||
self.offsets.append((0, 0))
|
||||
|
||||
return rv
|
||||
|
||||
class Position(Container):
|
||||
"""
|
||||
Controls the placement of a displayable on the screen, using
|
||||
@@ -141,7 +221,7 @@ class Position(Container):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
surf = self.child.render(width, height, st)
|
||||
surf = render(self.child, width, height, st)
|
||||
cw, ch = surf.get_size()
|
||||
|
||||
self.offsets = [ (0, 0) ]
|
||||
@@ -152,7 +232,76 @@ class Position(Container):
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
class Grid(Container):
|
||||
"""
|
||||
A grid is a widget that evenly allocates space to its children.
|
||||
The child widgets should not be greedy, but should instead be
|
||||
widgets that only use part of the space available to them.
|
||||
"""
|
||||
|
||||
def __init__(self, cols, rows, padding=0,
|
||||
style='default', **properties):
|
||||
"""
|
||||
@param cols: The number of columns in this widget.
|
||||
|
||||
@params rows: The number of rows in this widget.
|
||||
"""
|
||||
|
||||
super(Grid, self).__init__()
|
||||
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
self.cols = cols
|
||||
self.rows = rows
|
||||
|
||||
self.padding = padding
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
# For convenience and speed.
|
||||
padding = self.padding
|
||||
cols = self.cols
|
||||
rows = self.rows
|
||||
|
||||
if len(self.children) != cols * rows:
|
||||
raise Exception("Grid not completely full.")
|
||||
|
||||
renders = [ render(i, width, height, st) for i in self.children ]
|
||||
self.sizes = [ i.get_size() for i in renders ]
|
||||
|
||||
cwidth = 0
|
||||
cheight = 0
|
||||
|
||||
for w, h in self.sizes:
|
||||
cwidth = max(cwidth, w)
|
||||
cheight = max(cheight, h)
|
||||
|
||||
if self.style.xfill:
|
||||
cwidth = (width - (cols - 1) * padding) / cols
|
||||
|
||||
if self.style.yfill:
|
||||
cheight = (height - (rows - 1) * padding) / rows
|
||||
|
||||
width = cwidth * cols + padding * (cols - 1)
|
||||
height = cheight * rows + padding * (rows - 1)
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
self.offsets = [ ]
|
||||
|
||||
for y in range(0, rows):
|
||||
for x in range(0, cols):
|
||||
|
||||
child = self.children[ x + y * cols ]
|
||||
surf = renders[x + y * cols]
|
||||
|
||||
xpos = x * (cwidth + padding)
|
||||
ypos = y * (cheight + padding)
|
||||
|
||||
offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
|
||||
self.offsets.append(offset)
|
||||
|
||||
return rv
|
||||
|
||||
class HBox(Container):
|
||||
"""
|
||||
@@ -192,7 +341,7 @@ class HBox(Container):
|
||||
for i in self.children:
|
||||
|
||||
xoffsets.append(xo)
|
||||
surf = i.render(remwidth, height, st)
|
||||
surf = render(i, remwidth, height, st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
@@ -209,7 +358,7 @@ class HBox(Container):
|
||||
|
||||
width = xo - self.padding
|
||||
|
||||
rv = renpy.display.surface.Surface(width, myheight)
|
||||
rv = renpy.display.render.Render(width, myheight)
|
||||
|
||||
for surf, child, xo in zip(surfaces, self.children, xoffsets):
|
||||
sw, sh = surf.get_size()
|
||||
@@ -259,7 +408,7 @@ class VBox(Container):
|
||||
|
||||
yoffsets.append(yo)
|
||||
|
||||
surf = i.render(width, remheight, st)
|
||||
surf = render(i, width, remheight, st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
@@ -276,7 +425,7 @@ class VBox(Container):
|
||||
|
||||
height = yo - self.padding
|
||||
|
||||
rv = renpy.display.surface.Surface(mywidth, height)
|
||||
rv = renpy.display.render.Render(mywidth, height)
|
||||
|
||||
for surf, child, yo in zip(surfaces, self.children, yoffsets):
|
||||
|
||||
@@ -288,39 +437,6 @@ class VBox(Container):
|
||||
|
||||
return rv
|
||||
|
||||
class Fixed(Container):
|
||||
"""
|
||||
A container that lays out each of its children at fixed
|
||||
coordinates determined by the position style of the child. Each
|
||||
widget is given the whole area of this widget, and then placed
|
||||
within that area based on its position style.
|
||||
|
||||
The result of this layout is the size of the entire area allocated
|
||||
to it. So it's probably only viable for laying out a root window.
|
||||
"""
|
||||
|
||||
def __init__(self, style='default', **properties):
|
||||
super(Fixed, self).__init__()
|
||||
self.style = renpy.style.Style(style, properties)
|
||||
|
||||
def get_placement(self):
|
||||
return self.style
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
self.offsets = [ ]
|
||||
self.sizes = [ ]
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
|
||||
for child in self.children:
|
||||
surf = child.render(width, height, st)
|
||||
self.sizes.append(surf.get_size())
|
||||
|
||||
offset = child.place(rv, 0, 0, width, height, surf)
|
||||
self.offsets.append(offset)
|
||||
|
||||
return rv
|
||||
|
||||
class Window(Container):
|
||||
"""
|
||||
@@ -353,43 +469,64 @@ class Window(Container):
|
||||
# save typing and screen space.
|
||||
style = self.style
|
||||
|
||||
xminimum = scale(style.xminimum, width)
|
||||
yminimum = scale(style.yminimum, height)
|
||||
|
||||
left_margin = scale(style.left_margin, width)
|
||||
left_padding = scale(style.left_padding, width)
|
||||
|
||||
right_margin = scale(style.right_margin, width)
|
||||
right_padding = scale(style.right_padding, width)
|
||||
|
||||
top_margin = scale(style.top_margin, height)
|
||||
top_padding = scale(style.top_padding, height)
|
||||
|
||||
bottom_margin = scale(style.bottom_margin, height)
|
||||
bottom_padding = scale(style.bottom_padding, height)
|
||||
|
||||
# c for combined.
|
||||
cxmargin = left_margin + right_margin
|
||||
cymargin = top_margin + bottom_margin
|
||||
|
||||
cxpadding = left_padding + right_padding
|
||||
cypadding = top_padding + bottom_padding
|
||||
|
||||
# Render the child.
|
||||
surf = self.child.render(width - 2 * style.xmargin - 2 * style.xpadding,
|
||||
height - 2 * style.ymargin - 2 * style.ypadding,
|
||||
st)
|
||||
surf = render(self.child,
|
||||
width - cxmargin - cxpadding,
|
||||
height - cymargin - cypadding,
|
||||
st)
|
||||
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
# If we don't fill, shrink our size to fit.
|
||||
|
||||
if not style.xfill:
|
||||
width = max(2 * style.xmargin + 2 * style.xpadding + sw, style.xminimum)
|
||||
width = max(cxmargin + cxpadding + sw, xminimum)
|
||||
|
||||
if not style.yfill:
|
||||
height = max(2 * style.ymargin + 2 * style.ypadding + sh, style.yminimum)
|
||||
height = max(cymargin + cypadding + sh, yminimum)
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
# Draw the background. The background should render at exactly the
|
||||
# requested size. (That is, be a Frame or a Solid).
|
||||
if style.background:
|
||||
bw = width - 2 * style.xmargin
|
||||
bh = height - 2 * style.ymargin
|
||||
bw = width - cxmargin
|
||||
bh = height - cymargin
|
||||
|
||||
back = style.background.render(bw, bh, st)
|
||||
back = render(style.background, bw, bh, st)
|
||||
|
||||
rv.blit(back,
|
||||
(style.xmargin, style.ymargin))
|
||||
(left_margin, top_margin))
|
||||
# (0, 0, bw, bh))
|
||||
|
||||
offsets = self.child.place(rv,
|
||||
style.xmargin + style.xpadding,
|
||||
style.ymargin + style.ypadding,
|
||||
width - 2 * (style.xmargin + style.xpadding),
|
||||
height - 2 * (style.ymargin + style.ypadding),
|
||||
left_margin + left_padding,
|
||||
top_margin + top_padding,
|
||||
width - cxmargin - cxpadding,
|
||||
height - cymargin - cypadding,
|
||||
surf)
|
||||
|
||||
|
||||
self.offsets = [ offsets ]
|
||||
self.sizes = [ (sw, sh) ]
|
||||
@@ -435,7 +572,7 @@ class Pan(Container):
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
surf = self.child.render(width, height, st)
|
||||
surf = render(self.child, width, height, st)
|
||||
self.sizes = [ surf.get_size() ]
|
||||
|
||||
x0, y0 = self.startpos
|
||||
@@ -455,7 +592,7 @@ class Pan(Container):
|
||||
|
||||
self.offsets = [ (-xo, -yo) ]
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
# print surf
|
||||
|
||||
@@ -465,7 +602,7 @@ class Pan(Container):
|
||||
# rv.blit(surf, (-xo, -yo))
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -520,13 +657,13 @@ class Move(Container):
|
||||
|
||||
def render(self, width, height, st):
|
||||
self.st = st
|
||||
rv = self.child.render(width, height, st)
|
||||
rv = render(self.child, width, height, st)
|
||||
|
||||
self.sizes = [ rv.get_size() ]
|
||||
self.offsets = [ (0, 0) ]
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
@@ -0,0 +1,538 @@
|
||||
# Render lifespan.
|
||||
|
||||
# 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
|
||||
from pygame.constants import *
|
||||
|
||||
# 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
|
||||
|
||||
if color[3] == 255:
|
||||
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
|
||||
return surf
|
||||
|
||||
solid_cache = SolidCache()
|
||||
|
||||
## One thing to realize when considering the safety of this is that
|
||||
## if any widget producing a render is redrawn, all instances of that
|
||||
## render are killed, and so the entire thing is redrawn.
|
||||
|
||||
# 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
|
||||
# contents.)
|
||||
mutated_surfaces = { }
|
||||
|
||||
def render(widget, width, height, st):
|
||||
"""
|
||||
Renders a widget on the screen.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
rv.keep_alive()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
rv = widget.render(width, height, st)
|
||||
|
||||
rv.render_of.append((widget, width, height))
|
||||
|
||||
old_renders[widget, width, height] = rv
|
||||
new_renders[widget, width, height] = rv
|
||||
|
||||
return rv
|
||||
|
||||
# A list of (when, widget) for redraws.
|
||||
redraw_queue = [ ]
|
||||
|
||||
def process_redraws():
|
||||
"""
|
||||
Processes pending redraws. Returns True if a redraw is needed.
|
||||
"""
|
||||
|
||||
global redraw_queue
|
||||
redraw_queue.sort()
|
||||
|
||||
i = 0
|
||||
dead_widgets = sets.Set()
|
||||
now = time.time()
|
||||
|
||||
for when, widget in redraw_queue:
|
||||
|
||||
if when > now:
|
||||
break
|
||||
|
||||
i += 1
|
||||
|
||||
dead_widgets.add(widget)
|
||||
|
||||
if not dead_widgets:
|
||||
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):
|
||||
"""
|
||||
Call this to queue the redraw of the supplied widget in the
|
||||
supplied number of seconds.
|
||||
"""
|
||||
|
||||
redraw_queue.append((when + time.time(), widget))
|
||||
|
||||
def render_screen(widget, width, height, st):
|
||||
|
||||
global redraw_queue
|
||||
global old_renders
|
||||
global new_renders
|
||||
global mutated_surfaces
|
||||
|
||||
redraw_queue = [ ]
|
||||
mutated_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())
|
||||
|
||||
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()
|
||||
|
||||
return rv
|
||||
|
||||
old_blits = [ ]
|
||||
|
||||
|
||||
def compute_clip(source):
|
||||
"""
|
||||
This computes and returns the clipping rectangle of the source render.
|
||||
"""
|
||||
|
||||
global old_blits
|
||||
|
||||
new_blits = [ ]
|
||||
source.clip_to(pygame.display.get_surface(), 0, 0, new_blits)
|
||||
|
||||
bl0 = old_blits[:]
|
||||
bl1 = new_blits[:]
|
||||
|
||||
# Backup blits.
|
||||
old_blits = new_blits
|
||||
|
||||
# Changes between the two lists.
|
||||
changes = [ ]
|
||||
|
||||
|
||||
# Set of things in bl1.
|
||||
bl1set = { }
|
||||
for i in bl1:
|
||||
bl1set[i] = True
|
||||
|
||||
# indices.
|
||||
i0 = 0
|
||||
i1 = 0
|
||||
|
||||
while True:
|
||||
# If we're done with either of the lists, break.
|
||||
if i0 >= len(bl0) or i1 >= len(bl1):
|
||||
break
|
||||
|
||||
# blits
|
||||
b0 = bl0[i0]
|
||||
b1 = bl1[i1]
|
||||
|
||||
# If the two are the same.
|
||||
if b0 == b1:
|
||||
|
||||
# Only add if the surface is mutated.
|
||||
if b0[0] in mutated_surfaces:
|
||||
changes.append(b0)
|
||||
|
||||
i0 += 1
|
||||
i1 += 1
|
||||
continue
|
||||
|
||||
# If the surface is only in bl0.
|
||||
if b0 not in bl1set:
|
||||
changes.append(b0)
|
||||
i0 += 1
|
||||
|
||||
# The surface is only in bl1.
|
||||
else:
|
||||
changes.append(b1)
|
||||
i1 += 1
|
||||
|
||||
changes.extend(bl0[i0:])
|
||||
changes.extend(bl1[i1:])
|
||||
|
||||
if not changes:
|
||||
return None
|
||||
|
||||
surf, x0, y0, w, h = changes[0]
|
||||
x1 = x0 + w
|
||||
y1 = y0 + h
|
||||
|
||||
for surf, x, y, w, h in changes:
|
||||
x0 = min(x0, x)
|
||||
y0 = min(y0, y)
|
||||
|
||||
x1 = max(x1, x + w)
|
||||
y1 = max(y1, y + h)
|
||||
|
||||
return x0, y0, x1 - x0, y1 - y0
|
||||
|
||||
|
||||
def screen_blit(source, full=False):
|
||||
"""
|
||||
Blits the given render to the screen. Computes the difference
|
||||
between the current blit list and old_blits.
|
||||
"""
|
||||
|
||||
screen = pygame.display.get_surface()
|
||||
|
||||
if full:
|
||||
source.blit_to(screen, 0, 0)
|
||||
return (0, 0) + screen.get_size()
|
||||
|
||||
cliprect = compute_clip(source)
|
||||
|
||||
if not cliprect:
|
||||
return None
|
||||
|
||||
screen = pygame.display.get_surface()
|
||||
screen.set_clip(cliprect)
|
||||
|
||||
source.blit_to(screen, 0, 0)
|
||||
|
||||
screen.set_clip()
|
||||
|
||||
return cliprect
|
||||
|
||||
|
||||
|
||||
def mutated_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.
|
||||
"""
|
||||
|
||||
mutated_surfaces[id(surf)] = True
|
||||
|
||||
|
||||
class Render(object):
|
||||
"""
|
||||
A render represents a static picture of a single widget (perhaps
|
||||
including the images of all of the children of that widget). It
|
||||
is able to draw that widget to the screen, and contains
|
||||
information about when it becomes invalid (and therefore the
|
||||
widget can be withdrawn).
|
||||
"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
"""
|
||||
Creates a new render corresponding to the given widget with
|
||||
the specified width and height.
|
||||
|
||||
@param widget: If this render corresponds directly to a
|
||||
widget, then this is the widget it corresponds to.
|
||||
"""
|
||||
|
||||
# Just for safety's sake.
|
||||
self.dead = False
|
||||
|
||||
# A list of widget, width, height, corresponding to the
|
||||
# entries in old_renders that this render is in.
|
||||
self.render_of = [ ]
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
|
||||
self.parents = [ ]
|
||||
|
||||
self.blittables = [ ]
|
||||
self.children = [ ]
|
||||
self.depends = [ ]
|
||||
|
||||
# A pygame surface holding this Render, if one exists.
|
||||
self.surface = None
|
||||
self.surface_alpha = False
|
||||
|
||||
self.subsurfaces = { }
|
||||
|
||||
# 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 i in self.children:
|
||||
|
||||
# assert self in i.parents
|
||||
|
||||
# assert not i.dead
|
||||
|
||||
i.keep_alive()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
if self.dead:
|
||||
return
|
||||
|
||||
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 = [ ]
|
||||
|
||||
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)
|
||||
|
||||
self.blittables.append((x, y, source))
|
||||
|
||||
|
||||
def blit_to(self, dest, x, y):
|
||||
"""
|
||||
This blits the children of this Render 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 clip_to(self, dest, x, y, blits):
|
||||
"""
|
||||
This fills in blits with (id(surf), x, y, w, h) tuples.
|
||||
"""
|
||||
|
||||
for xo, yo, source in self.blittables:
|
||||
|
||||
if isinstance(source, pygame.Surface):
|
||||
blits.append((id(source), x + xo, y + yo) + source.get_size())
|
||||
else:
|
||||
source.clip_to(dest, x + xo, y + yo, blits)
|
||||
|
||||
def fill(self, color):
|
||||
"""
|
||||
Fake a pygame.Surface.fill()
|
||||
"""
|
||||
|
||||
surf = solid_cache.create((self.width, self.height), color)
|
||||
self.blit(surf, (0,0))
|
||||
|
||||
def get_size(self):
|
||||
"""
|
||||
Returns the size of this Render, a mostly ficticious value
|
||||
that's taken from the inputs to the constructor. (As in, we
|
||||
don't clip to this size.)
|
||||
"""
|
||||
|
||||
return self.width, self.height
|
||||
|
||||
def pygame_surface(self, alpha=True):
|
||||
"""
|
||||
Returns a pygame surface constructed from this Render. This
|
||||
may return a cached surface, if one already has been rendered
|
||||
(so you probably shouldn't change the output of this much).
|
||||
"""
|
||||
|
||||
if self.surface and self.surface_alpha == alpha:
|
||||
return self.surface
|
||||
|
||||
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)
|
||||
|
||||
self.surface = rv
|
||||
self.surface_alpha = alpha
|
||||
|
||||
mutated_surface(rv)
|
||||
|
||||
return rv
|
||||
|
||||
def subsurface(self, pos):
|
||||
"""
|
||||
Returns a subsurface of this render.
|
||||
"""
|
||||
|
||||
if pos in self.subsurfaces:
|
||||
return self.subsurfaces[pos]
|
||||
|
||||
x, y, width, height = pos
|
||||
|
||||
if x > self.width or y > self.height:
|
||||
return Render(0, 0)
|
||||
|
||||
width = min(self.width - x, width)
|
||||
height = min(self.height - y, height)
|
||||
|
||||
rv = Render(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()
|
||||
|
||||
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))
|
||||
|
||||
|
||||
self.subsurfaces[pos] = rv
|
||||
rv.depends_on(self)
|
||||
|
||||
return rv
|
||||
|
||||
def depends_on(self, child):
|
||||
"""
|
||||
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)
|
||||
|
||||
|
||||
+466
-9
@@ -1,28 +1,483 @@
|
||||
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):
|
||||
def get_font(fn, size, bold=False, italics=False, underline=False):
|
||||
from renpy.loader import transfn
|
||||
|
||||
if (fn, size) in _font_cache:
|
||||
return _font_cache[(fn, size)]
|
||||
if (fn, size, bold, italics, underline) in _font_cache:
|
||||
return _font_cache[(fn, size, bold, italics, underline)]
|
||||
|
||||
try:
|
||||
rv = pygame.font.Font(transfn(fn), size)
|
||||
rv.set_bold(bold)
|
||||
rv.set_italic(italics)
|
||||
except:
|
||||
rv = pygame.font.SysFont(fn, size)
|
||||
rv = pygame.font.SysFont(fn, size, bold, italics)
|
||||
|
||||
_font_cache[(fn, size)] = rv
|
||||
rv.set_underline(underline)
|
||||
|
||||
_font_cache[(fn, size, bold, italics, underline)] = 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_linesize()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
nosave = [ 'laidout', 'laidout_lineheights', 'laidout_width', 'laidout_height', 'width' ]
|
||||
|
||||
def after_setstate(self):
|
||||
self.laidout = None
|
||||
|
||||
def __init__(self, text, slow=False, style='default', **properties):
|
||||
"""
|
||||
@param text: The text that will be displayed on the screen.
|
||||
|
||||
@param slow: If True, the text will be slowly typed onto the screen.
|
||||
|
||||
@param style: A style that will be applied to the text.
|
||||
|
||||
@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
|
||||
|
||||
def set_text(self, new_text):
|
||||
"""
|
||||
Changes the text display by this object to new_text.
|
||||
"""
|
||||
|
||||
self.text = new_text
|
||||
self._update()
|
||||
|
||||
def set_style(self, style):
|
||||
"""
|
||||
Changes the style assocated with this object.
|
||||
"""
|
||||
|
||||
self.style = style
|
||||
self._update()
|
||||
|
||||
def _update(self):
|
||||
"""
|
||||
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()
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
lines = [ ]
|
||||
line = [ ]
|
||||
|
||||
# The height of the current line, in pixels, not including
|
||||
# line_height_fudge.
|
||||
lineheight = 0
|
||||
|
||||
# A list of same.
|
||||
lineheights = [ ]
|
||||
|
||||
# The width of the current line.
|
||||
linewidth = 0
|
||||
|
||||
# The maximum linewidth.
|
||||
maxwidth = 0
|
||||
|
||||
# The current text.
|
||||
cur = ""
|
||||
|
||||
# The width, in pixels, of cur.
|
||||
curwidth = 0
|
||||
|
||||
# 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)
|
||||
|
||||
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))
|
||||
|
||||
else:
|
||||
raise Exception("Text tag %s was not recognized. Case and spacing matter here.")
|
||||
|
||||
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_height_fudge
|
||||
|
||||
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_height_fudge = self.style.line_height_fudge
|
||||
|
||||
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_height_fudge
|
||||
|
||||
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
|
||||
|
||||
absxo = abs(dsxo)
|
||||
absyo = abs(dsyo)
|
||||
|
||||
width -= absxo
|
||||
|
||||
if dsxo < 0:
|
||||
xo = -dsxo
|
||||
dsxo = 0
|
||||
else:
|
||||
xo = 0
|
||||
|
||||
if dsyo < 0:
|
||||
yo = -dsyo
|
||||
dsyo = 0
|
||||
else:
|
||||
yo = 0
|
||||
|
||||
self.layout(width - absxo)
|
||||
|
||||
rv = renpy.display.render.Render(self.laidout_width + absxo, self.laidout_height + absxo)
|
||||
|
||||
if self.style.drop_shadow:
|
||||
self.render_pass(rv, dsxo, dsyo, self.style.drop_shadow_color, False, length)
|
||||
|
||||
self.slow = not self.render_pass(rv, xo, yo, self.style.color, True, length)
|
||||
|
||||
if self.slow:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
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()
|
||||
|
||||
class OldText(renpy.display.core.Displayable):
|
||||
"""
|
||||
A Displayable that can display text on the screen.
|
||||
"""
|
||||
@@ -54,6 +509,7 @@ 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)
|
||||
@@ -96,8 +552,7 @@ class Text(renpy.display.core.Displayable):
|
||||
Called to split the text into a string with newline characters
|
||||
at line endings where wrapping has occured.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
lines = [ ]
|
||||
@@ -154,7 +609,7 @@ class Text(renpy.display.core.Displayable):
|
||||
if not hasattr(self, "laidout"):
|
||||
self.layout(width - dsxo)
|
||||
|
||||
surf = renpy.display.surface.Surface(self.width + dsxo, self.height + dsyo)
|
||||
surf = renpy.display.render.Render(self.width + dsxo, self.height + dsyo)
|
||||
font = get_font(self.style.font, self.style.size)
|
||||
|
||||
laidout = self.laidout
|
||||
@@ -164,7 +619,7 @@ class Text(renpy.display.core.Displayable):
|
||||
chars = int(st * renpy.config.annoying_text_cps)
|
||||
if chars < len(laidout):
|
||||
laidout = laidout[:chars]
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
else:
|
||||
self.slow = False
|
||||
else:
|
||||
@@ -186,6 +641,7 @@ class Text(renpy.display.core.Displayable):
|
||||
|
||||
for l in lines:
|
||||
ls = font.render(l, self.style.antialias, color)
|
||||
renpy.display.render.mutated_surface(ls)
|
||||
|
||||
lw, lh = ls.get_size()
|
||||
lw += indent
|
||||
@@ -242,3 +698,4 @@ class ParameterizedText(object):
|
||||
|
||||
return Text(string, style=self.style, **self.properties)
|
||||
|
||||
|
||||
|
||||
+433
-43
@@ -1,7 +1,57 @@
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
import pygame
|
||||
from pygame.constants import *
|
||||
|
||||
# This is a utility function that attempts to refactor an old and a new
|
||||
# Fixed into four Fixeds: below, old, new, and above. Since only the
|
||||
# old and new need transitions, this can be a significant win.
|
||||
def refactor_fixed(in_old, in_new):
|
||||
|
||||
Fixed = renpy.display.layout.Fixed
|
||||
|
||||
out_below = Fixed()
|
||||
out_old = Fixed()
|
||||
out_new = Fixed()
|
||||
out_above = Fixed()
|
||||
|
||||
if (not isinstance(in_old, Fixed)) or (not isinstance(in_new, Fixed)):
|
||||
return out_below, in_old, in_new, out_above
|
||||
|
||||
old_list = in_old.get_widget_time_list()
|
||||
new_list = in_new.get_widget_time_list()
|
||||
|
||||
# Merge the beginnings of the lists.
|
||||
while old_list and new_list:
|
||||
if old_list[0] == new_list[0]:
|
||||
out_below.add(new_list[0][0], new_list[0][1])
|
||||
old_list.pop(0)
|
||||
new_list.pop(0)
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
# Merge the ends of the lists.
|
||||
above_list = [ ]
|
||||
|
||||
while old_list and new_list:
|
||||
if old_list[-1] == new_list[-1]:
|
||||
above_list.insert(0, new_list[-1])
|
||||
old_list.pop()
|
||||
new_list.pop()
|
||||
else:
|
||||
break
|
||||
|
||||
for widget, time in above_list:
|
||||
out_above.add(widget, time)
|
||||
|
||||
for widget, time in old_list:
|
||||
out_old.add(widget, time)
|
||||
|
||||
for widget, time in new_list:
|
||||
out_new.add(widget, time)
|
||||
|
||||
return out_below, out_old, out_new, out_above
|
||||
|
||||
class Transition(renpy.display.core.Displayable):
|
||||
"""
|
||||
@@ -12,38 +62,47 @@ class Transition(renpy.display.core.Displayable):
|
||||
def __init__(self, delay):
|
||||
self.delay = delay
|
||||
self.offsets = [ ]
|
||||
self.events = True
|
||||
|
||||
def event(self, ev, x, y):
|
||||
event_list = self.new_scene_list[:]
|
||||
event_list.reverse()
|
||||
|
||||
offsets = self.offsets[:]
|
||||
offsets.reverse()
|
||||
|
||||
for (key, st, disp), (xo, yo) in zip(event_list, offsets):
|
||||
rv = disp.event(ev, x - xo, y - yo)
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
return None
|
||||
if self.events:
|
||||
return self.new_widget.event(ev, x, y)
|
||||
else:
|
||||
return None
|
||||
|
||||
class Fade(Transition):
|
||||
"""
|
||||
This is a transition that involves fading to a certain color, then
|
||||
holding that color for a certain amount of time, then fading in the
|
||||
new scene.
|
||||
This returns an object that can be used as an argument to a with
|
||||
statement to fade the old scene into a solid color, waits for a
|
||||
given amount of time, and then fades from the solid color into
|
||||
the new scene.
|
||||
|
||||
@param in_time: The amount of time that will be spent
|
||||
fading from the old scene to the solid color. A float, given as
|
||||
seconds.
|
||||
|
||||
@param hold_time: The amount of time that will be spent
|
||||
displaying the solid color. A float, given as seconds.
|
||||
|
||||
@param out_time: The amount of time that will be spent
|
||||
fading from the solid color to the new scene. A float, given as
|
||||
seconds.
|
||||
|
||||
@param color: The solid color that will be faded
|
||||
to. This is an RGB triple, where each element is in the range 0
|
||||
to 255. This defaults to black.
|
||||
"""
|
||||
|
||||
def __init__(self, out_time, hold_time, in_time,
|
||||
old_scene_list, new_scene_list, color=(0, 0, 0)):
|
||||
old_widget=None, new_widget=None, color=(0, 0, 0)):
|
||||
|
||||
super(Fade, self).__init__(out_time + hold_time + in_time)
|
||||
|
||||
self.out_time = out_time
|
||||
self.hold_time = hold_time
|
||||
self.in_time = in_time
|
||||
self.old_scene_list = old_scene_list
|
||||
self.new_scene_list = new_scene_list
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
self.color = color
|
||||
|
||||
# self.frames = 0
|
||||
@@ -55,31 +114,29 @@ class Fade(Transition):
|
||||
|
||||
# self.frames += 1
|
||||
|
||||
rv = renpy.display.surface.Surface(width, height)
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
events = False
|
||||
|
||||
if st < self.out_time:
|
||||
scene_list = self.old_scene_list
|
||||
widget = self.old_widget
|
||||
alpha = int(255 * (st / self.out_time))
|
||||
|
||||
elif st < self.out_time + self.hold_time:
|
||||
scene_list = None
|
||||
widget = None
|
||||
alpha = 255
|
||||
|
||||
else:
|
||||
scene_list = self.new_scene_list
|
||||
widget = self.new_widget
|
||||
alpha = 255 - int(255 * ((st - self.out_time - self.hold_time) / self.in_time))
|
||||
events = True
|
||||
|
||||
if scene_list:
|
||||
surf, offsets = renpy.display.core.render_scene_list(scene_list,
|
||||
width,
|
||||
height)
|
||||
if widget:
|
||||
surf = render(widget, width, height, st)
|
||||
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
if events:
|
||||
self.offsets = offsets
|
||||
self.events = events
|
||||
|
||||
# Just to be sure.
|
||||
if alpha < 0:
|
||||
@@ -91,35 +148,368 @@ class Fade(Transition):
|
||||
rv.fill(self.color[:3] + (alpha,))
|
||||
|
||||
if st < self.in_time + self.hold_time + self.out_time:
|
||||
renpy.game.interface.redraw(0)
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
return rv
|
||||
|
||||
class Dissolve(Transition):
|
||||
# This was a nifty idea that just didn't work out, since we can't vary
|
||||
# the alpha on an image with an alpha channel. Too bad.
|
||||
|
||||
def __init__(self, time, old_scene_list, new_scene_list):
|
||||
# class Dissolve(Transition):
|
||||
|
||||
# def __init__(self, time, old_widget, new_widget):
|
||||
# super(Dissolve, self).__init__(time)
|
||||
|
||||
# self.time = time
|
||||
# self.below, self.old, self.new, self.above = refactor_fixed(old_widget, new_widget)
|
||||
|
||||
# def event(self, ev, x, y):
|
||||
|
||||
# rv = self.above.event(ev, x, y)
|
||||
|
||||
# if rv is None:
|
||||
# rv = self.new.event(ev, x, y)
|
||||
|
||||
# if rv is None:
|
||||
# rv = self.below.event(ev, x, y)
|
||||
|
||||
# return rv
|
||||
|
||||
# def render(self, width, height, st):
|
||||
|
||||
# rv = renpy.display.render.Render(width, height)
|
||||
|
||||
# # Below.
|
||||
# below = render(self.below, width, height, st)
|
||||
# rv.blit(below, (0, 0))
|
||||
|
||||
# if st < self.time:
|
||||
# # Old.
|
||||
# old = render(self.old, width, height, st)
|
||||
# rv.blit(old, (0, 0))
|
||||
|
||||
# # New.
|
||||
# alpha = min(255, int(255 * st / self.time))
|
||||
# new = render(self.new, width, height, st)
|
||||
|
||||
# if alpha < 255:
|
||||
# surf = new.pygame_surface(False)
|
||||
# renpy.display.render.mutable_surface(surf)
|
||||
# surf.set_alpha(alpha, RLEACCEL)
|
||||
# rv.blit(surf, (0, 0))
|
||||
# rv.depends_on(new)
|
||||
# else:
|
||||
# rv.blit(new, (0, 0))
|
||||
|
||||
# # Above.
|
||||
# above = render(self.above, width, height, st)
|
||||
# rv.blit(above, (0, 0))
|
||||
|
||||
|
||||
# if st < self.time:
|
||||
# renpy.display.render.redraw(self, 0)
|
||||
|
||||
# return rv
|
||||
|
||||
|
||||
|
||||
class Dissolve(Transition):
|
||||
"""
|
||||
This dissolves from the old scene to the new scene, by
|
||||
overlaying the new scene on top of the old scene and varying its
|
||||
alpha from 0 to 255.
|
||||
|
||||
@param delay: The amount of time the dissolve will take.
|
||||
"""
|
||||
|
||||
def __init__(self, time, old_widget=None, new_widget=None):
|
||||
super(Dissolve, self).__init__(time)
|
||||
|
||||
self.time = time
|
||||
self.old_scene_list = old_scene_list
|
||||
self.new_scene_list = new_scene_list
|
||||
self.old_widget = old_widget
|
||||
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):
|
||||
|
||||
rsl = renpy.display.core.render_scene_list
|
||||
if st >= self.time:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
rv, offsets = rsl(self.old_scene_list, width, height)
|
||||
surftree, self.offsets = rsl(self.new_scene_list, width, height)
|
||||
surf = surftree.pygame_surface(False)
|
||||
if st < self.time:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
alpha = min(255, int(255 * st / self.time))
|
||||
|
||||
surf.set_alpha(alpha)
|
||||
rv.blit(surf, (0, 0))
|
||||
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)
|
||||
|
||||
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))
|
||||
surf.set_alpha(alpha, RLEACCEL)
|
||||
rv.blit(surf, (0, 0))
|
||||
|
||||
self.old_alpha = alpha / 255.0
|
||||
|
||||
|
||||
self.old_top = id(top)
|
||||
self.old_bottom = id(bottom)
|
||||
|
||||
if st < self.time:
|
||||
renpy.game.interface.redraw(0)
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
class CropMove(Transition):
|
||||
"""
|
||||
The CropMove transition works by placing the old and the new image
|
||||
on two layers, called the top and the bottom. (Normally the new
|
||||
image is on the top, but that can be changed in some modes.) The
|
||||
bottom layer is always drawn in full. The top image is first
|
||||
cropped to a rectangle, and then that rectangle drawn onto
|
||||
the screen at a specified position. Start and end crop rectangles
|
||||
and positions can be selected by the supplied mode, or
|
||||
specified manually. The result is a surprisingly flexible
|
||||
transition.
|
||||
|
||||
This transition has many modes, simplifying its use. We can group
|
||||
these modes into three groups: wipes, slides, and other.
|
||||
|
||||
In a wipe, the image stays fixed, and more of it is revealed as
|
||||
the transition progresses. For example, in "wiperight", a wipe from left to right, first the left edge of the image is
|
||||
revealed at the left edge of the screen, then the center of the image,
|
||||
and finally the right side of the image at the right of the screen.
|
||||
Other supported wipes are "wipeleft", "wipedown", and "wipeup".
|
||||
|
||||
In a slide, the image moves. So in a "slideright", the right edge of the
|
||||
image starts at the left edge of the screen, and moves to the right
|
||||
as the transition progresses. Other slides are "slideleft", "slidedown",
|
||||
and "slideup".
|
||||
|
||||
There are also slideaways, in which the old image moves on top of
|
||||
the new image. Slideaways include "slideawayright", "slideawayleft",
|
||||
"slideawayup", and "slideawaydown".
|
||||
|
||||
We also support a rectangular iris in with "irisin" and a
|
||||
rectangular iris out with "irisout". Finally, "custom" lets the
|
||||
user define new transitions, if these ones are not enough.
|
||||
"""
|
||||
|
||||
def __init__(self, time,
|
||||
mode="fromleft",
|
||||
startcrop=(0.0, 0.0, 0.0, 1.0),
|
||||
startpos=(0.0, 0.0),
|
||||
endcrop=(0.0, 0.0, 1.0, 1.0),
|
||||
endpos=(0.0, 0.0),
|
||||
topnew=True,
|
||||
old_widget=None,
|
||||
new_widget=None):
|
||||
|
||||
"""
|
||||
@param time: The time that this transition will last for, in seconds.
|
||||
|
||||
@param mode: One of the modes given above.
|
||||
|
||||
The following parameters are only respected if the mode is "custom".
|
||||
|
||||
@param startcrop: The starting rectangle that is cropped out of the
|
||||
top image. A 4-element tuple containing x, y, width, and height.
|
||||
|
||||
@param startpos: The starting place that the top image is drawn
|
||||
to the screen at, a 2-element tuple containing x and y.
|
||||
|
||||
@param startcrop: The starting rectangle that is cropped out of the
|
||||
top image. A 4-element tuple containing x, y, width, and height.
|
||||
|
||||
@param startpos: The starting place that the top image is drawn
|
||||
to the screen at, a 2-element tuple containing x and y.
|
||||
|
||||
@param topnew: If True, the top layer contains the new
|
||||
image. Otherwise, the top layer contains the old image.
|
||||
"""
|
||||
|
||||
|
||||
self.time = time
|
||||
|
||||
if mode == "wiperight":
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 0.0, 1.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "wipeleft":
|
||||
startpos = (1.0, 0.0)
|
||||
startcrop = (1.0, 0.0, 0.0, 1.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "wipedown":
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 0.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "wipeup":
|
||||
startpos = (0.0, 1.0)
|
||||
startcrop = (0.0, 1.0, 1.0, 0.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "slideright":
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (1.0, 0.0, 0.0, 1.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "slideleft":
|
||||
startpos = (1.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 0.0, 1.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "slideup":
|
||||
startpos = (0.0, 1.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 0.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "slidedown":
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 1.0, 1.0, 0.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "slideawayleft":
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (1.0, 0.0, 0.0, 1.0)
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = False
|
||||
|
||||
elif mode == "slideawayright":
|
||||
endpos = (1.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 0.0, 1.0)
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = False
|
||||
|
||||
elif mode == "slideawaydown":
|
||||
endpos = (0.0, 1.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 0.0)
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = False
|
||||
|
||||
elif mode == "slideawayup":
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 1.0, 1.0, 0.0)
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = False
|
||||
|
||||
elif mode == "irisout":
|
||||
startpos = (0.5, 0.5)
|
||||
startcrop = (0.5, 0.5, 0.0, 0.0)
|
||||
endpos = (0.0, 0.0)
|
||||
endcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
topnew = True
|
||||
|
||||
elif mode == "irisin":
|
||||
startpos = (0.0, 0.0)
|
||||
startcrop = (0.0, 0.0, 1.0, 1.0)
|
||||
endpos = (0.5, 0.5)
|
||||
endcrop = (0.5, 0.5, 0.0, 0.0)
|
||||
topnew = False
|
||||
|
||||
|
||||
elif mode == "custom":
|
||||
pass
|
||||
else:
|
||||
raise Exception("Invalid mode %s passed into boxwipe." % mode)
|
||||
|
||||
self.delay = time
|
||||
self.time = time
|
||||
|
||||
self.startpos = startpos
|
||||
self.endpos = endpos
|
||||
|
||||
self.startcrop = startcrop
|
||||
self.endcrop = endcrop
|
||||
|
||||
self.topnew = topnew
|
||||
|
||||
self.old_widget = old_widget
|
||||
self.new_widget = new_widget
|
||||
|
||||
self.events = False
|
||||
|
||||
if topnew:
|
||||
self.bottom = old_widget
|
||||
self.top = new_widget
|
||||
else:
|
||||
self.bottom = new_widget
|
||||
self.top = old_widget
|
||||
|
||||
def render(self, width, height, st):
|
||||
|
||||
time = 1.0 * st / self.time
|
||||
|
||||
# Done rendering.
|
||||
if time >= 1.0:
|
||||
self.events = True
|
||||
return render(self.new_widget, width, height, st)
|
||||
|
||||
# How we scale each element of a tuple.
|
||||
scales = (width, height, width, height)
|
||||
|
||||
def interpolate_tuple(t0, t1):
|
||||
return tuple([ int(s * (a * (1.0 - time) + b * time))
|
||||
for a, b, s in zip(t0, t1, scales) ])
|
||||
|
||||
crop = interpolate_tuple(self.startcrop, self.endcrop)
|
||||
pos = interpolate_tuple(self.startpos, self.endpos)
|
||||
|
||||
rv = renpy.display.render.Render(width, height)
|
||||
|
||||
rv.blit(render(self.bottom, width, height, st), (0, 0))
|
||||
|
||||
top = render(self.top, width, height, st)
|
||||
ss = top.subsurface(crop)
|
||||
rv.blit(ss, pos)
|
||||
|
||||
renpy.display.render.redraw(self, 0)
|
||||
return rv
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import renpy
|
||||
from renpy.display.render import render
|
||||
import pygame
|
||||
import sys # for maxint
|
||||
|
||||
class MovieInfo(object):
|
||||
|
||||
def __init__(self, filename, loops, fullscreen, size=None):
|
||||
self.filename = filename
|
||||
self.loops = loops + 1
|
||||
self.fullscreen = fullscreen
|
||||
self.size = size
|
||||
|
||||
# The movie that is currently playing, if any.
|
||||
movie = None
|
||||
|
||||
# If the movie is running in a widget, this is the surface corresponding
|
||||
# to that widget.
|
||||
surface = None
|
||||
|
||||
# The current movie info.
|
||||
current_info = None
|
||||
|
||||
# The number of loops the current movie has made.
|
||||
loops = 0
|
||||
|
||||
def movie_stop(clear=True):
|
||||
"""
|
||||
This stops the currently playing movie.
|
||||
"""
|
||||
|
||||
global movie
|
||||
global loops
|
||||
|
||||
if movie:
|
||||
movie.stop()
|
||||
movie = None
|
||||
surface = None
|
||||
loops = 0
|
||||
|
||||
renpy.display.audio.enable_mixer()
|
||||
|
||||
if clear:
|
||||
renpy.game.context().scene_lists.movie = None
|
||||
|
||||
|
||||
def movie_start_fullscreen(filename, loops=0):
|
||||
"""
|
||||
This starts a MPEG-1 movie playing in fullscreen mode. While the movie is
|
||||
playing (that is, until the next call to movie_stop), interactions will
|
||||
not display anything on the screen.
|
||||
|
||||
@param filename: The filename of the MPEG-1 move that we're playing.
|
||||
|
||||
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
|
||||
"""
|
||||
|
||||
movie_stop()
|
||||
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, True)
|
||||
|
||||
def movie_start_displayable(filename, size, loops=0):
|
||||
"""
|
||||
This starts a MPEG-1 movie playing in displayable mode. One or more Movie()
|
||||
widgets must be displayed if the movie is to be shown to the user.
|
||||
|
||||
@param filename: The filename of the MPEG-1 move that we're playing.
|
||||
|
||||
@param size: A tuple containing the size of the movie on the screen. For example, (640, 480).
|
||||
|
||||
@param loops: The number of additional times the movie should be looped. -1 to loop it forever.
|
||||
"""
|
||||
|
||||
movie_stop()
|
||||
renpy.game.context().scene_lists.movie = MovieInfo(filename, loops, False, size)
|
||||
|
||||
|
||||
def interact():
|
||||
"""
|
||||
This is called at the start of an interaction. It starts the required
|
||||
movie playing, if it's necessary. It returns True if the movie is fullscreen
|
||||
and therefore nothing else should be drawn on the screen, or False
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
try:
|
||||
|
||||
global movie
|
||||
global surface
|
||||
global current_info
|
||||
global loops
|
||||
|
||||
info = renpy.game.context().scene_lists.movie
|
||||
|
||||
# Has the info changed? If so, stop the movie.
|
||||
if info is not current_info:
|
||||
movie_stop(False)
|
||||
current_info = info
|
||||
|
||||
# No movie to play.
|
||||
if not info:
|
||||
return False
|
||||
|
||||
# Movie not playing, start it up.
|
||||
if not movie:
|
||||
|
||||
# Needed so we get movie sound.
|
||||
renpy.display.audio.disable_mixer()
|
||||
|
||||
m = pygame.movie.Movie(renpy.loader.transfn(info.filename))
|
||||
|
||||
if info.fullscreen:
|
||||
s = None
|
||||
|
||||
m.set_display(pygame.display.get_surface(),
|
||||
(0, 0,
|
||||
renpy.config.screen_width,
|
||||
renpy.config.screen_height))
|
||||
else:
|
||||
s = pygame.Surface(info.size)
|
||||
m.set_display(s, (0, 0) + info.size)
|
||||
|
||||
movie = m
|
||||
surface = s
|
||||
|
||||
if not movie.get_busy():
|
||||
if not info.loops or loops < info.loops:
|
||||
movie.rewind()
|
||||
movie.play()
|
||||
loops += 1
|
||||
else:
|
||||
movie_stop()
|
||||
|
||||
|
||||
# Movie is playing (by now).
|
||||
return info.fullscreen
|
||||
|
||||
except:
|
||||
movie_stop()
|
||||
|
||||
if renpy.config.debug_sound:
|
||||
raise
|
||||
else:
|
||||
renpy.display.audio.enable_mixer()
|
||||
return False
|
||||
|
||||
|
||||
class Movie(renpy.display.layout.Null):
|
||||
"""
|
||||
This is a displayable that displays the current movie. In general,
|
||||
a movie should be playing whenever this is on the screen.
|
||||
That movie should have been started using movie_start_displayable
|
||||
before this is shown on the screen, and hidden before this is
|
||||
removed.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, style='image_placement', **properties):
|
||||
super(Movie, self).__init__(style=style, **properties)
|
||||
|
||||
def render(self, width, height, st):
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
|
||||
if surface:
|
||||
renpy.display.render.mutated_surface(surface)
|
||||
|
||||
w, h = surface.get_size()
|
||||
rv = renpy.display.render.Render(w, h)
|
||||
rv.blit(surface, (0, 0))
|
||||
return rv
|
||||
else:
|
||||
return super(Movie, self).render(width, height, st)
|
||||
|
||||
+1
-2
@@ -36,8 +36,7 @@ class Context(object):
|
||||
oldsl = context.scene_lists
|
||||
self.runtime = context.runtime
|
||||
|
||||
import renpy.display.core as dcore
|
||||
self.scene_lists = dcore.SceneLists(oldsl)
|
||||
self.scene_lists = renpy.display.core.SceneLists(oldsl)
|
||||
|
||||
def goto_label(self, node_name):
|
||||
"""
|
||||
|
||||
+115
-23
@@ -12,8 +12,9 @@ from renpy.display.behavior import *
|
||||
from renpy.display.image import *
|
||||
|
||||
from renpy.curry import curry
|
||||
from renpy.music import music_start, music_stop
|
||||
from renpy.sound import play
|
||||
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 *
|
||||
from renpy.python import py_eval as eval
|
||||
from renpy.python import rng as random
|
||||
@@ -32,8 +33,8 @@ def checkpoint():
|
||||
|
||||
renpy.game.log.checkpoint()
|
||||
|
||||
def interact(*widgets, **kwargs):
|
||||
return renpy.game.interface.interact(transient=widgets, **kwargs)
|
||||
# def interact(**kwargs):
|
||||
# return renpy.game.interface.interact(**kwargs)
|
||||
|
||||
def scene_lists(index=-1):
|
||||
"""
|
||||
@@ -111,7 +112,7 @@ def watch(expression, style='default', **properties):
|
||||
|
||||
renpy.config.overlay_functions.append(overlay_func)
|
||||
|
||||
def input(prompt, default='', length=None):
|
||||
def input(prompt, default='', allow=None, exclude='{}', length=None):
|
||||
"""
|
||||
This pops up a window requesting that the user enter in some text.
|
||||
It returns the entered text.
|
||||
@@ -122,15 +123,23 @@ def input(prompt, default='', 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.
|
||||
"""
|
||||
|
||||
vbox = renpy.display.layout.VBox()
|
||||
win = renpy.display.layout.Window(vbox, style='input_window')
|
||||
renpy.ui.window(style='input_window')
|
||||
renpy.ui.vbox()
|
||||
|
||||
vbox.add(renpy.display.text.Text(prompt, style='input_prompt'))
|
||||
vbox.add(renpy.display.behavior.Input(default, length=length, style='input_text'))
|
||||
renpy.ui.text(prompt, style='input_prompt')
|
||||
renpy.ui.input(default, length=length, style='input_text', allow=allow, exclude=exclude)
|
||||
|
||||
return interact(win)
|
||||
renpy.ui.close()
|
||||
|
||||
return renpy.ui.interact()
|
||||
|
||||
def menu(items, set_expr):
|
||||
"""
|
||||
@@ -186,11 +195,24 @@ def display_menu(items, window_style='menu_window'):
|
||||
renpy.ui.window(style=window_style)
|
||||
renpy.ui.menu(items)
|
||||
|
||||
rv = interact()
|
||||
rv = renpy.ui.interact()
|
||||
checkpoint()
|
||||
|
||||
return rv
|
||||
|
||||
class TagQuotingDict(object):
|
||||
def __getitem__(self, key):
|
||||
if key in renpy.game.store:
|
||||
rv = renpy.game.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):
|
||||
"""
|
||||
@@ -200,7 +222,7 @@ def say(who, what):
|
||||
"""
|
||||
|
||||
# Interpolate variables.
|
||||
what = what % renpy.game.store
|
||||
what = what % tag_quoting_dict
|
||||
|
||||
if who is None:
|
||||
who = renpy.store.narrator
|
||||
@@ -231,7 +253,7 @@ def display_say(who, what, who_style='say_label',
|
||||
"""
|
||||
|
||||
# If we're going to do an interaction, then saybehavior needs
|
||||
# to be here, right before ui.text.
|
||||
# to be here.
|
||||
if interact:
|
||||
renpy.ui.saybehavior()
|
||||
|
||||
@@ -290,7 +312,7 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
|
||||
|
||||
renpy.ui.keymousebehavior()
|
||||
|
||||
rv = interact(suppress_overlay=(not overlays))
|
||||
rv = renpy.ui.interact(suppress_overlay=(not overlays))
|
||||
checkpoint()
|
||||
return rv
|
||||
|
||||
@@ -298,7 +320,9 @@ def imagemap(ground, selected, hotspots, unselected=None, overlays=False,
|
||||
def pause(delay=None, music=None):
|
||||
"""
|
||||
When called, this pauses and waits for the user to click before
|
||||
advancing the script.
|
||||
advancing the script. If given a delay parameter, the Ren'Py will
|
||||
wait for that amount of time before continuing, unless a user clicks to
|
||||
interrupt the delay.
|
||||
|
||||
@param delay: The number of seconds to delay.
|
||||
|
||||
@@ -313,15 +337,45 @@ def pause(delay=None, music=None):
|
||||
"""
|
||||
|
||||
if music is not None:
|
||||
newdelay = renpy.music.music_delay(music)
|
||||
newdelay = renpy.display.audio.music_delay(music)
|
||||
|
||||
if newdelay is not None:
|
||||
delay = newdelay
|
||||
|
||||
sayb = renpy.display.behavior.SayBehavior(delay=delay)
|
||||
scene_list_add('transient', sayb)
|
||||
|
||||
return interact()
|
||||
renpy.ui.saybehavior()
|
||||
|
||||
if delay:
|
||||
renpy.ui.pausebehavior(delay, False)
|
||||
|
||||
return renpy.ui.interact()
|
||||
|
||||
def movie_cutscene(filename, delay, loops=0):
|
||||
"""
|
||||
This displays an MPEG-1 cutscene for the specified number of
|
||||
seconds. The user can click to interrupt the cutscene.
|
||||
Overlays and Underlays are disabled for the duration of the cutscene.
|
||||
|
||||
@param filename: The name of a file containing an MPEG-1 movie.
|
||||
|
||||
@param delay: The number of seconds to wait before ending the cutscene. Normally the length of the movie, in seconds.
|
||||
|
||||
@param loops: The number of extra loops to show, -1 to loop forever.
|
||||
|
||||
Returns True if the movie was terminated by the user, or False if the
|
||||
given delay elapsed uninterrupted.
|
||||
"""
|
||||
|
||||
movie_start_fullscreen(filename, loops=loops)
|
||||
|
||||
renpy.ui.saybehavior()
|
||||
renpy.ui.pausebehavior(delay, False)
|
||||
|
||||
rv = renpy.ui.interact(suppress_overlay=True, suppress_underlay=True, show_mouse=False)
|
||||
|
||||
movie_stop()
|
||||
|
||||
return rv
|
||||
|
||||
|
||||
def with(trans):
|
||||
"""
|
||||
@@ -339,7 +393,7 @@ def with(trans):
|
||||
renpy.game.interface.set_transition(trans)
|
||||
return renpy.game.interface.interact(show_mouse=False,
|
||||
trans_pause=True,
|
||||
suppress_overlay=True)
|
||||
suppress_overlay=not renpy.config.overlay_during_wait)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -423,17 +477,29 @@ def windows():
|
||||
import sys
|
||||
return hasattr(sys, 'winver')
|
||||
|
||||
def transition(trans):
|
||||
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
|
||||
interaction. This is useful when the next interaction doesn't take
|
||||
a with clause, as is the case with pause, input, and imagemap.
|
||||
|
||||
@param layer: If the layer setting is not None, then the transition
|
||||
will be applied only to the layer named. Please note that only some
|
||||
transitions can be applied to specific layers.
|
||||
"""
|
||||
|
||||
if trans is None:
|
||||
renpy.game.interface.with_none()
|
||||
else:
|
||||
renpy.game.interface.set_transition(trans)
|
||||
renpy.game.interface.set_transition(trans, layer)
|
||||
|
||||
def clear_game_runtime():
|
||||
"""
|
||||
@@ -458,6 +524,32 @@ def get_game_runtime():
|
||||
|
||||
return renpy.game.context().runtime / 1000.0
|
||||
|
||||
def exists(filename):
|
||||
"""
|
||||
Returns true if the given filename can be found in the
|
||||
searchpath. This only works if a physical file exists on disk. It
|
||||
won't find the file if it's inside of an archive.
|
||||
"""
|
||||
|
||||
try:
|
||||
renpy.loader.transfn(filename)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
def restart_interaction():
|
||||
"""
|
||||
Calling this restarts the current interaction. This will immediately end
|
||||
any ongoing transition, and will call all of the overlay functions again.
|
||||
|
||||
This should be called whenever widgets are added or removed over the course
|
||||
of an interaction, or when the information used to construct the overlay
|
||||
changes.
|
||||
"""
|
||||
|
||||
renpy.game.interface.restart_interaction = True
|
||||
|
||||
|
||||
call_in_new_context = renpy.game.call_in_new_context
|
||||
curried_call_in_new_context = renpy.curry.curry(renpy.game.call_in_new_context)
|
||||
|
||||
|
||||
+1
-1
@@ -147,4 +147,4 @@ def call_in_new_context(label):
|
||||
context.run()
|
||||
|
||||
contexts.pop()
|
||||
interface.redraw(0)
|
||||
interface.force_redraw = True
|
||||
|
||||
+5
-1
@@ -15,8 +15,12 @@ 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, id(o)
|
||||
print prefix, "@%x" % id(o)
|
||||
return
|
||||
|
||||
seen[id(o)] = True
|
||||
|
||||
+1
-1
@@ -574,7 +574,7 @@ class RollbackLog(renpy.object.Object):
|
||||
rb.rollback()
|
||||
|
||||
# Disable the next transition, as it's pointless.
|
||||
renpy.game.interface.supress_transition = True
|
||||
renpy.game.interface.suppress_transition = True
|
||||
|
||||
# If necessary, reset the RNG.
|
||||
if force:
|
||||
|
||||
+9
-1
@@ -91,7 +91,15 @@ class Script(object):
|
||||
raise Exception("Could not load %s or %s." % (fn, alt))
|
||||
|
||||
|
||||
self.initcode.sort()
|
||||
# Make the sort stable.
|
||||
initcode = [ (prio, index, code) for index, (prio, code) in
|
||||
enumerate(self.initcode) ]
|
||||
|
||||
initcode.sort()
|
||||
|
||||
self.initcode = [ (prio, code) for prio, index, code in initcode ]
|
||||
|
||||
|
||||
|
||||
# Do some generic init here.
|
||||
|
||||
|
||||
+14
-45
@@ -22,12 +22,14 @@ Image = renpy.display.image.Image
|
||||
Solid = renpy.display.image.Solid
|
||||
Frame = renpy.display.image.Frame
|
||||
Animation = renpy.display.image.Animation
|
||||
Movie = renpy.display.video.Movie
|
||||
|
||||
Position = renpy.curry.curry(renpy.display.layout.Position)
|
||||
Pan = renpy.curry.curry(renpy.display.layout.Pan)
|
||||
Move = renpy.curry.curry(renpy.display.layout.Move)
|
||||
|
||||
Fade = renpy.curry.curry(renpy.display.transition.Fade)
|
||||
Dissolve = renpy.curry.curry(renpy.display.transition.Dissolve)
|
||||
CropMove = renpy.curry.curry(renpy.display.transition.CropMove)
|
||||
|
||||
def _return(v):
|
||||
"""
|
||||
@@ -98,11 +100,12 @@ class Character(object):
|
||||
self.window_style = window_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what):
|
||||
def __call__(self, what, interact=True):
|
||||
renpy.display_say(self.name, what,
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
interact=interact,
|
||||
**self.properties)
|
||||
|
||||
class DynamicCharacter(object):
|
||||
@@ -134,7 +137,7 @@ class DynamicCharacter(object):
|
||||
self.window_style = window_style
|
||||
self.properties = properties
|
||||
|
||||
def __call__(self, what):
|
||||
def __call__(self, what, interact=True):
|
||||
import renpy.python as python
|
||||
|
||||
renpy.display_say(python.py_eval(self.name_expr),
|
||||
@@ -142,56 +145,22 @@ class DynamicCharacter(object):
|
||||
who_style=self.who_style,
|
||||
what_style=self.what_style,
|
||||
window_style=self.window_style,
|
||||
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
|
||||
|
||||
def narrator(what):
|
||||
renpy.display_say(None, what, what_style='say_thought')
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
|
||||
+17
-1
@@ -1,7 +1,15 @@
|
||||
import renpy
|
||||
|
||||
# A list of style prefixes we care about, including no prefix.
|
||||
prefixes = [ 'hover_', 'idle_', '' ]
|
||||
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' ],
|
||||
)
|
||||
|
||||
class StyleManager(object):
|
||||
"""
|
||||
@@ -61,6 +69,7 @@ class Style(object):
|
||||
"""
|
||||
|
||||
def __getstate__(self):
|
||||
|
||||
return dict(properties=self.properties,
|
||||
prefix=self.prefix,
|
||||
parent=self.parent)
|
||||
@@ -76,6 +85,13 @@ class Style(object):
|
||||
|
||||
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
|
||||
|
||||
|
||||
+110
-24
@@ -16,11 +16,10 @@
|
||||
|
||||
import renpy
|
||||
|
||||
# The current widget (None to add widgets directly to the
|
||||
# transient display list.)
|
||||
current = None
|
||||
# The current widget. (Should never become None.)
|
||||
current = 'transient'
|
||||
|
||||
# A stack of current widgets.
|
||||
# A stack of current widgets and/or layers.
|
||||
current_stack = [ ]
|
||||
|
||||
# True if the current widget should be used at most once.
|
||||
@@ -36,12 +35,12 @@ def interact(**kwargs):
|
||||
@param show_mouse: Should the mouse be shown during this
|
||||
interaction? Only advisory, as this doesn't work reliably.
|
||||
|
||||
@param suppress_overlay: This suppresses the display of the overlay during
|
||||
this interaction.
|
||||
@param suppress_overlay: This suppresses the display of the overlay
|
||||
during this interaction.
|
||||
"""
|
||||
|
||||
if current_stack:
|
||||
raise Exception("ui.interact called with non-empty widget stack. Did you forget a ui.close()?")
|
||||
raise Exception("ui.interact called with non-empty widget/layer stack. Did you forget a ui.close() somewhere?")
|
||||
|
||||
return renpy.game.interface.interact(**kwargs)
|
||||
|
||||
@@ -56,8 +55,8 @@ def add(w, make_current=False, once=False):
|
||||
global current
|
||||
global current_once
|
||||
|
||||
if current is None:
|
||||
renpy.game.context(-1).scene_lists.add('transient', w)
|
||||
if isinstance(current, str):
|
||||
renpy.game.context(-1).scene_lists.add(current, w)
|
||||
else:
|
||||
current.add(w)
|
||||
|
||||
@@ -73,19 +72,38 @@ def add(w, make_current=False, once=False):
|
||||
|
||||
return w
|
||||
|
||||
|
||||
def layer(name):
|
||||
"""
|
||||
This causes widgets to be added to the named layer, until a
|
||||
matching call to ui.close().
|
||||
"""
|
||||
|
||||
global current_once
|
||||
global current
|
||||
|
||||
if not isinstance(current, str):
|
||||
raise Exception("Opening a layer while a widget is open is not allowed.")
|
||||
|
||||
if name not in renpy.config.layers:
|
||||
raise Exception("'%s' is not a known layer." % name)
|
||||
|
||||
current_stack.append(current)
|
||||
current_once = False
|
||||
current = name
|
||||
|
||||
def close():
|
||||
"""
|
||||
Closes the current widget. This means that we will no longer add
|
||||
things to the current widget, but will instead start adding things
|
||||
to the parent of that widget, or the screen if the widget has no
|
||||
parent. Calling this when there is no current widget will lead to
|
||||
an exception being thrown.
|
||||
This closes the currently open widget or layer. If a widget is
|
||||
closed, then we start adding to its parent, or the layer if no
|
||||
parent is open. If a layer is closed, we return to the previously
|
||||
open layer. An error is thrown if we close the last open layer.
|
||||
"""
|
||||
|
||||
global current
|
||||
|
||||
if current is None:
|
||||
raise Exception("ui.close() called when there is no open widget.")
|
||||
if not current_stack:
|
||||
raise Exception("ui.close() called to close the last open layer or widget.")
|
||||
|
||||
if current_once:
|
||||
raise Exception("ui.close() called when expecting a widget.")
|
||||
@@ -148,6 +166,36 @@ def vbox(padding=0, **properties):
|
||||
|
||||
return add(renpy.display.layout.VBox(padding, **properties), True)
|
||||
|
||||
def grid(cols, rows, padding=0, xfill=False, yfill=False, **properties):
|
||||
"""
|
||||
This creates a layout that places widgets in an evenly spaced
|
||||
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
|
||||
the grid.)
|
||||
|
||||
The children of this widget should have a fixed size that does not
|
||||
vary based on the space allocated to them. Failure to observe this
|
||||
restriction could lead to really odd layouts, or things being
|
||||
rendered off screen.
|
||||
|
||||
Each cell of the grid is exactly the same size. By default, the
|
||||
grid is the smallest size that can accommodate all of its
|
||||
children, but it can be expanded to consume all available space in
|
||||
a given dimension by setting xfill or yfill to True, as appropriate.
|
||||
|
||||
@param cols: The number of columns in this grid.
|
||||
@param rows: The number of rows in this grid.
|
||||
@param padding: The amount of space to leave between rows and columns.
|
||||
@param xfill: True if the grid should consume all available width.
|
||||
@param yfill: True if the grid should consume all available height.
|
||||
"""
|
||||
|
||||
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
|
||||
@@ -204,14 +252,34 @@ def saybehavior():
|
||||
"""
|
||||
This is a psuedo-widget that adds the say behavior to the
|
||||
screen. The say behavior is to return True if the left mouse is
|
||||
clicked or enter is pressed. It also returns true in various other
|
||||
cases if the current statement has already been seen. This widget
|
||||
clicked or enter is pressed. It also returns True in various other
|
||||
cases, such as if the current statement has already been seen. This widget
|
||||
should not be added to any other widget, but should instead be
|
||||
only added to the screen itself.
|
||||
"""
|
||||
|
||||
return add(renpy.display.behavior.SayBehavior())
|
||||
|
||||
def pausebehavior(delay, result=False):
|
||||
"""
|
||||
This is a psuedo-widget that adds the pause behavior to the
|
||||
screen. The pause behavior is to return the supplied result when
|
||||
the given number of seconds elapses. This widget should not be
|
||||
added to any other widget, but should instead be only added to the
|
||||
screen itself.
|
||||
|
||||
Please note that this widget will always pause for the given
|
||||
amount of time. If you want a pause that can be interrupted by
|
||||
the user, add in a saybehavior.
|
||||
|
||||
@param delay: The amount of time to pause, in seconds.
|
||||
|
||||
@param result: The result that will be retuned after the delay time
|
||||
elapses.
|
||||
"""
|
||||
|
||||
return add(renpy.display.behavior.PauseBehavior(delay, result))
|
||||
|
||||
def menu(menuitems, **properties):
|
||||
"""
|
||||
This creates a new menu widget. Unlike the menu statement or
|
||||
@@ -221,12 +289,30 @@ def menu(menuitems, **properties):
|
||||
@param menuitems: A list of tuples that are the items to be added
|
||||
to this menu. The first element of a tuple is a string that is
|
||||
used for this menuitem. The second element is the value to be
|
||||
returned from renpy.interact() if this item is selected, or None
|
||||
returned from ui.interact() if this item is selected, or None
|
||||
if this item is a non-selectable caption.
|
||||
"""
|
||||
|
||||
return add(renpy.display.behavior.Menu(menuitems, **properties))
|
||||
|
||||
def input(default, length=None, allow=None, exclude='{}', **properties):
|
||||
"""
|
||||
This creats a new input widget. This widget accepts textual input
|
||||
until the user hits enter, and then returns that text.
|
||||
|
||||
@param default: The default text that fills the input.
|
||||
|
||||
@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))
|
||||
|
||||
def image(filename, **properties):
|
||||
"""
|
||||
@@ -241,7 +327,7 @@ def imagemap(ground, selected, hotspots, unselected=None,
|
||||
"""
|
||||
This is the widget that implements imagemaps. Parameters are
|
||||
roughtly the same as renpy.imagemap. The value of the hotspot is
|
||||
returned when renpy.interact() returns.
|
||||
returned when ui.interact() returns.
|
||||
"""
|
||||
|
||||
return add(renpy.display.image.ImageMap(ground, selected,
|
||||
@@ -254,7 +340,7 @@ def button(clicked=None, **properties):
|
||||
This creates a button that can be clicked by the user. When this
|
||||
button is clicked or otherwise selected, the function supplied as
|
||||
the clicked argument is called. If it returns a value, that value
|
||||
is returned from renpy.interact().
|
||||
is returned from ui.interact().
|
||||
|
||||
Buttons created with this function contain another widget,
|
||||
specifically the next widget to be added. As a convenience, one
|
||||
@@ -272,7 +358,7 @@ def textbutton(text, clicked=None, text_style='button_text', **properties):
|
||||
This creates a button that is labelled with some text. When the
|
||||
button is clicked or otherwise selected, the function supplied as
|
||||
the clicked argument is called. If it returns a value, that value
|
||||
is returned from renpy.interact().
|
||||
is returned from ui.interact().
|
||||
|
||||
@param text: The text of this button.
|
||||
|
||||
@@ -295,7 +381,7 @@ def imagebutton(idle_image, hover_image, clicked=None,
|
||||
while the second is the hover image, which is used when the mouse
|
||||
is over the image. If the button is clicked or otherwise selected,
|
||||
then the clicked argument is called. If it returns a value, that
|
||||
value is returned from renpy.interact().
|
||||
value is returned from ui.interact().
|
||||
|
||||
@param idle_image: The file name of the image used when this
|
||||
button is idle.
|
||||
@@ -337,7 +423,7 @@ def bar(width, height, range, value, clicked=None, **properties):
|
||||
widget. It is called with a single argument, which is the value
|
||||
corresponding to the location at which the mouse button was clicked.
|
||||
If this function returns a value, that value is returned from
|
||||
renpy.interact().
|
||||
ui.interact().
|
||||
|
||||
For best results, if clicked is set then width should be at least
|
||||
twice as big as range.
|
||||
|
||||
+56
-10
@@ -2,7 +2,8 @@
|
||||
|
||||
import os.path
|
||||
|
||||
# Go psyco! (Compile where we can.)
|
||||
# Enable psyco. Warning: Check for memory leaks!
|
||||
|
||||
try:
|
||||
if not os.path.exists("nopsyco"):
|
||||
import psyco
|
||||
@@ -24,12 +25,7 @@ import encodings.zlib_codec
|
||||
# Load up all of Ren'Py, in the right order.
|
||||
import renpy
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
|
||||
# Stdout should be a utf-8 stream, so print works nicely.
|
||||
# utf8writer = codecs.getwriter("utf-8")
|
||||
# sys.stdout = utf8writer(sys.stdout)
|
||||
def main():
|
||||
|
||||
name = os.path.basename(sys.argv[0])
|
||||
|
||||
@@ -51,6 +47,9 @@ if __name__ == "__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:
|
||||
@@ -72,6 +71,16 @@ if __name__ == "__main__":
|
||||
|
||||
type, value, tb = sys.exc_info()
|
||||
|
||||
|
||||
print >>f, type.__name__ + ":",
|
||||
print >>f, unicode(e).encode('utf-8')
|
||||
print >>f
|
||||
print >>f, renpy.game.exception_info
|
||||
|
||||
print >>f
|
||||
print >>f, "-- Full Traceback ------------------------------------------------------------"
|
||||
print >>f
|
||||
|
||||
traceback.print_tb(tb, None, sys.stdout)
|
||||
traceback.print_tb(tb, None, f)
|
||||
|
||||
@@ -96,8 +105,45 @@ if __name__ == "__main__":
|
||||
os.startfile('traceback.txt')
|
||||
except:
|
||||
pass
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
+30
-8
@@ -1,6 +1,8 @@
|
||||
init:
|
||||
$ config.window_title = "Test Game"
|
||||
# $ config.profile = True
|
||||
$ config.profile = True
|
||||
|
||||
$ config.debug_sound = True
|
||||
|
||||
# Set up the size of the screen.
|
||||
$ config.screen_width = 800
|
||||
@@ -20,8 +22,8 @@ init:
|
||||
# Styles.
|
||||
$ style.window.background = Frame("frame.png", 120, 25)
|
||||
|
||||
$ style.menu_choice.hover_sound = "chev1.wav"
|
||||
$ style.menu_choice.activate_sound = "chev2.wav"
|
||||
$ style.menu.hover_sound = "chev1.wav"
|
||||
$ style.menu.activate_sound = "chev2.wav"
|
||||
|
||||
$ style.imagemap.hover_sound = "chev3.wav"
|
||||
$ style.imagemap.activate_sound = "chev4.wav"
|
||||
@@ -45,6 +47,8 @@ init:
|
||||
"9a_vhappy.png", 0.25,
|
||||
"9a_concerned.png")
|
||||
|
||||
image movie = Movie()
|
||||
|
||||
# Character objects.
|
||||
$ e = Character('Eileen', color=(200, 255, 200, 255))
|
||||
$ pov = DynamicCharacter('pov_name', color=(255, 0, 0, 255))
|
||||
@@ -78,6 +82,7 @@ label main_menu:
|
||||
# Stuff that happens before the real main menu.
|
||||
|
||||
scene black
|
||||
|
||||
|
||||
show text "American Bishoujo\nPresents" \
|
||||
at Move((0.5, 0.0), (0.5, 0.5), 4.0,
|
||||
@@ -111,14 +116,16 @@ label start:
|
||||
ui.interact()
|
||||
|
||||
|
||||
$ the_value = 10
|
||||
|
||||
label foo:
|
||||
|
||||
|
||||
"Hey, let's make sure that the
|
||||
multi-line text\ \ \ still
|
||||
works right."
|
||||
|
||||
$ renpy.movie_cutscene("On_Your_Mark.mpg", 450.0)
|
||||
$ the_value = 10
|
||||
|
||||
label foo:
|
||||
|
||||
python hide:
|
||||
|
||||
def bar_clicked(value):
|
||||
@@ -130,7 +137,7 @@ label start:
|
||||
ui.textbutton("Continue", clicked = lambda : False)
|
||||
ui.close()
|
||||
|
||||
rv = renpy.interact()
|
||||
rv = ui.interact()
|
||||
|
||||
if rv is not False:
|
||||
store.the_value = rv
|
||||
@@ -139,6 +146,21 @@ label start:
|
||||
|
||||
|
||||
|
||||
$ renpy.movie_start_displayable('On_Your_Mark.mpg', (400, 300))
|
||||
show movie
|
||||
|
||||
p "I'm now showing a movie."
|
||||
|
||||
p "It's using alpha blending and all that."
|
||||
|
||||
p "Bully!"
|
||||
|
||||
$ renpy.movie_stop()
|
||||
hide movie
|
||||
|
||||
|
||||
|
||||
|
||||
p "This is a test."
|
||||
|
||||
$ overtext = 'This is overtext.'
|
||||
|
||||
Reference in New Issue
Block a user