Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c5cdd0038e | |||
| 8bac98c29a | |||
| 421d8cc37d | |||
| c097434036 | |||
| e3cc4be1b8 | |||
| b5711f7e11 | |||
| 8f2649e3a7 | |||
| ab42c0f6e1 | |||
| 01eacd2932 | |||
| c25f22fe4f | |||
| fe3b800cab | |||
| ccde74bdb8 | |||
| ab6f469811 | |||
| f92b70d38c | |||
| e89338342a | |||
| 62ca262aec | |||
| eb5a84d420 | |||
| 3280bcc5ff | |||
| 5c5f72659c | |||
| b9789ae25c | |||
| 58e56cff88 | |||
| 4b532ac11e | |||
| a7f16eee3f | |||
| fff2558083 | |||
| 944b7d0e9c | |||
| 7022001dbe | |||
| 0ab9444676 | |||
| 0795565e07 | |||
| 3cf31edae2 | |||
| 8b356090d9 | |||
| 9f3be6e604 | |||
| 0e6522f867 | |||
| 3cbe6fa1d8 | |||
| bb5588f182 | |||
| 45a6a08559 | |||
| e1cb42f253 | |||
| dde19b8716 | |||
| 906788fe88 | |||
| 69b019f03c | |||
| 9de1c93ef9 | |||
| c4f478160b | |||
| 054535f0c8 | |||
| 16b9c2be67 | |||
| 4a591d552b | |||
| 576a1138d3 | |||
| 50ef339f16 | |||
| 0385bbcb9b | |||
| 20a5d6cd69 | |||
| 34a1f57e50 | |||
| 5245fbb38e | |||
| 937c91f314 | |||
| d90f1cdc30 | |||
| 8c82667ae1 | |||
| 3085c15caf | |||
| 2e37df7103 | |||
| 9a75b6628b | |||
| 405d8d4399 | |||
| 56dda5590b | |||
| 86046383a8 | |||
| f907fe119a | |||
| 14ced3377a | |||
| ad690d8a16 | |||
| 1c830a0be1 | |||
| ee89717b47 | |||
| 6cfef151da | |||
| 8d2a6d2596 | |||
| f225453ff1 | |||
| 209eb580cf | |||
| 3103cff7f7 | |||
| 138884b545 | |||
| 1489a4e748 | |||
| 1a7fb9c008 | |||
| 6506a3a45e | |||
| 3913ca9c0c | |||
| 4e03602bd6 | |||
| a2e08864c4 | |||
| 14563943ed | |||
| 5e91783620 | |||
| cde01cf27b |
+1
-1
@@ -252,7 +252,7 @@ def main():
|
||||
"-q",
|
||||
"egg_info",
|
||||
"--tag-build",
|
||||
"+renpy" + args.version,
|
||||
"+renpy" + args.version.replace("+", "-"),
|
||||
"sdist",
|
||||
"-d",
|
||||
os.path.abspath(destination)
|
||||
|
||||
+1
-1
@@ -328,7 +328,7 @@ define gui.vbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.
|
||||
define gui.vscrollbar_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
define gui.vslider_borders = Borders(gui.scale(4), gui.scale(4), gui.scale(4), gui.scale(4))
|
||||
|
||||
## What to do with unscrollable scrollbars in the gui. "hide" hides them, while
|
||||
## What to do with unscrollable scrollbars in the game menu. "hide" hides them, while
|
||||
## None shows them.
|
||||
define gui.unscrollable = "hide"
|
||||
|
||||
|
||||
@@ -1531,7 +1531,8 @@ fix_dlc("renios", "renios")
|
||||
|
||||
update = { variant : { "version" : self.update_versions[variant], "base_name" : self.base_name, "files" : update_files, "directories" : update_directories, "xbit" : update_xbit } }
|
||||
|
||||
update_fn = os.path.join(self.destination, filename + ".update.json")
|
||||
update_fn = self.temp_filename(filename + ".update.json")
|
||||
|
||||
|
||||
if self.include_update and not format.startswith("app-"):
|
||||
|
||||
@@ -1565,9 +1566,11 @@ fix_dlc("renios", "renios")
|
||||
in this thread or a background thread.
|
||||
"""
|
||||
|
||||
if self.include_update and not self.build_update and not dlc:
|
||||
final_update_fn = os.path.join(self.destination, filename + ".update.json")
|
||||
|
||||
if self.build_update or dlc:
|
||||
if os.path.exists(update_fn):
|
||||
os.unlink(update_fn)
|
||||
shutil.copy(update_fn, final_update_fn)
|
||||
|
||||
if not directory:
|
||||
file_hash = hash_file(path)
|
||||
|
||||
@@ -169,7 +169,7 @@ def _check_hash(filename, hashj):
|
||||
download_file = ""
|
||||
download_url = ""
|
||||
|
||||
def download(url, filename, hash=None):
|
||||
def download(url, filename, hash=None, headers=None, requests_kwargs={}):
|
||||
"""
|
||||
Downloads `url` to `filename`, a tempfile.
|
||||
"""
|
||||
@@ -188,9 +188,13 @@ def download(url, filename, hash=None):
|
||||
|
||||
progress_time = time.time()
|
||||
|
||||
all_headers = {"User-Agent": 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36 renpy/8'}
|
||||
if headers:
|
||||
all_headers.update(headers)
|
||||
|
||||
try:
|
||||
|
||||
response = requests.get(url, stream=True, proxies=renpy.exports.proxies, timeout=15)
|
||||
response = requests.get(url, stream=True, proxies=renpy.exports.proxies, timeout=15, headers=all_headers, **requests_kwargs)
|
||||
response.raise_for_status()
|
||||
|
||||
total_size = int(response.headers.get('content-length', 1))
|
||||
|
||||
@@ -301,6 +301,8 @@ init python:
|
||||
build.classify_renpy("**.new", None)
|
||||
build.classify_renpy("**.bak", None)
|
||||
|
||||
build.classify_renpy("**.keystore", None)
|
||||
|
||||
build.classify_renpy("**/log.txt", None)
|
||||
build.classify_renpy("**/traceback.txt", None)
|
||||
build.classify_renpy("**/errors.txt", None)
|
||||
|
||||
@@ -17,6 +17,8 @@ root = ""
|
||||
|
||||
import renpy
|
||||
|
||||
class BannedException(Exception):
|
||||
pass
|
||||
|
||||
class WebHandler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
@@ -59,7 +61,13 @@ class WebHandler(http.server.BaseHTTPRequestHandler):
|
||||
None, in which case the caller has nothing further to do.
|
||||
|
||||
"""
|
||||
path = self.translate_path(self.path)
|
||||
|
||||
try:
|
||||
path = self.translate_path(self.path)
|
||||
except BannedException:
|
||||
self.send_error(403, "File not found")
|
||||
return None
|
||||
|
||||
f = None
|
||||
if os.path.isdir(path):
|
||||
parts = urllib.parse.urlsplit(self.path)
|
||||
@@ -192,6 +200,9 @@ class WebHandler(http.server.BaseHTTPRequestHandler):
|
||||
path = root
|
||||
|
||||
for word in words:
|
||||
if word.startswith("."):
|
||||
raise BannedException("Invalid path.")
|
||||
|
||||
if os.path.dirname(word) or word in (os.curdir, os.pardir):
|
||||
# Ignore components that are not a simple file/directory name
|
||||
continue
|
||||
|
||||
@@ -1202,7 +1202,7 @@ void RPS_set_secondary_volume(int channel, float vol2, float delay) {
|
||||
/**
|
||||
* Replaces audio filters with the given PyObject.
|
||||
*/
|
||||
void RPS_replace_audio_filter(int channel, PyObject *new_filter) {
|
||||
void RPS_replace_audio_filter(int channel, PyObject *new_filter, int primary) {
|
||||
|
||||
struct Channel *c;
|
||||
|
||||
@@ -1214,10 +1214,13 @@ void RPS_replace_audio_filter(int channel, PyObject *new_filter) {
|
||||
|
||||
LOCK_AUDIO();
|
||||
|
||||
if (c->playing_audio_filter) {
|
||||
Py_DECREF(c->playing_audio_filter);
|
||||
Py_INCREF(new_filter);
|
||||
c->playing_audio_filter = new_filter;
|
||||
if (primary) {
|
||||
|
||||
if (c->playing_audio_filter) {
|
||||
Py_DECREF(c->playing_audio_filter);
|
||||
Py_INCREF(new_filter);
|
||||
c->playing_audio_filter = new_filter;
|
||||
}
|
||||
}
|
||||
|
||||
if (c->queued_audio_filter) {
|
||||
|
||||
@@ -43,7 +43,7 @@ void RPS_set_volume(int channel, float volume);
|
||||
float RPS_get_volume(int channel);
|
||||
void RPS_set_pan(int channel, float pan, float delay);
|
||||
void RPS_set_secondary_volume(int channel, float vol2, float delay);
|
||||
void RPS_replace_audio_filter(int channel, PyObject *audio_filter);
|
||||
void RPS_replace_audio_filter(int channel, PyObject *audio_filter, int primary);
|
||||
|
||||
int RPS_video_ready(int channel);
|
||||
PyObject *RPS_read_video(int channel);
|
||||
|
||||
+10
-9
@@ -1146,6 +1146,16 @@ compatible_pairs = [
|
||||
# values of the variables here.
|
||||
|
||||
|
||||
def check_spline_types(value):
|
||||
if isinstance(value, (position, int, float)):
|
||||
return True
|
||||
|
||||
if isinstance(value, tuple):
|
||||
return all(check_spline_types(i) for i in value)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class RawMultipurpose(RawStatement):
|
||||
|
||||
warp_function = None
|
||||
@@ -1208,15 +1218,6 @@ class RawMultipurpose(RawStatement):
|
||||
|
||||
compiling(self.loc)
|
||||
|
||||
def check_spline_types(value):
|
||||
if isinstance(value, (position, int, float)):
|
||||
return True
|
||||
|
||||
if isinstance(value, tuple):
|
||||
return all(check_spline_types(i) for i in value)
|
||||
|
||||
return False
|
||||
|
||||
# Figure out what kind of statement we have. If there's no
|
||||
# interpolator, and no properties, than we have either a
|
||||
# call, or a child statement.
|
||||
|
||||
+26
-11
@@ -230,6 +230,7 @@ class Channel(object):
|
||||
|
||||
# The audio filter to use.
|
||||
audio_filter = None
|
||||
raw_audio_filter = None
|
||||
|
||||
def __init__(self, name, default_loop, stop_on_mute, tight, file_prefix, file_suffix, buffer_queue, movie, framedrop, synchro_start):
|
||||
|
||||
@@ -364,9 +365,12 @@ class Channel(object):
|
||||
ExecutionContext to point to the copy, and returns the copy.
|
||||
"""
|
||||
|
||||
mcd = renpy.game.context().music
|
||||
context = renpy.game.context()
|
||||
mcd = dict(context.music)
|
||||
context.music = mcd
|
||||
|
||||
ctx = self.get_context().copy()
|
||||
|
||||
mcd[self.name] = ctx
|
||||
return ctx
|
||||
|
||||
@@ -690,6 +694,7 @@ class Channel(object):
|
||||
|
||||
old_raw_audio_filter = self.context.raw_audio_filter
|
||||
self.context.raw_audio_filter = audio_filter
|
||||
self.raw_audio_filter = audio_filter
|
||||
|
||||
if old_raw_audio_filter is None and audio_filter is None:
|
||||
new_audio_filter = None
|
||||
@@ -701,11 +706,14 @@ class Channel(object):
|
||||
|
||||
self.context.audio_filter = new_audio_filter
|
||||
|
||||
if replace:
|
||||
for q in self.queue:
|
||||
q.audio_filter = new_audio_filter
|
||||
for q in self.queue:
|
||||
q.audio_filter = new_audio_filter
|
||||
|
||||
if replace:
|
||||
renpysound.replace_audio_filter(self.number, new_audio_filter, 1)
|
||||
else:
|
||||
renpysound.replace_audio_filter(self.number, new_audio_filter, 0)
|
||||
|
||||
renpysound.replace_audio_filter(self.number, new_audio_filter)
|
||||
|
||||
def enqueue(self, filenames, loop=True, synchro_start=None, fadein=0, tight=None, loop_only=False, relative_volume=1.0):
|
||||
|
||||
@@ -905,17 +913,20 @@ def register_channel(name,
|
||||
Ren'Py will display frames late rather than dropping them.
|
||||
|
||||
`synchro_start`
|
||||
Does this channel particpate in synchro start? Synchro start determines if
|
||||
Does this channel participate in synchro start? Synchro start determines if
|
||||
the channel will start playing at the same time as other channels. If None,
|
||||
this defaults to `loop`.
|
||||
this defaults to `loop` if `movie` is False, and False otherwise.
|
||||
"""
|
||||
|
||||
if synchro_start is None:
|
||||
synchro_start = loop
|
||||
|
||||
if name == "movie":
|
||||
movie = True
|
||||
|
||||
if synchro_start is None:
|
||||
if movie:
|
||||
synchro_start = False
|
||||
else:
|
||||
synchro_start = loop
|
||||
|
||||
if not force and not renpy.game.context().init_phase and (" " not in name):
|
||||
raise Exception("Can't register channel outside of init phase.")
|
||||
|
||||
@@ -1260,6 +1271,7 @@ def interact():
|
||||
|
||||
ctx = c.context
|
||||
|
||||
|
||||
# If we're in the same music change, then do nothing with the
|
||||
# music.
|
||||
if c.last_changed == ctx.last_changed:
|
||||
@@ -1268,11 +1280,14 @@ def interact():
|
||||
filenames = ctx.last_filenames
|
||||
tight = ctx.last_tight
|
||||
|
||||
if ctx.raw_audio_filter != c.raw_audio_filter:
|
||||
c.set_audio_filter(ctx.raw_audio_filter, True)
|
||||
|
||||
if c.loop != filenames:
|
||||
c.fadeout(max(renpy.config.context_fadeout_music, renpy.config.fadeout_audio))
|
||||
|
||||
if filenames:
|
||||
c.enqueue(filenames, loop=True, synchro_start=True, tight=tight, fadein=renpy.config.context_fadein_music, relative_volume=ctx.last_relative_volume)
|
||||
c.enqueue(filenames, loop=True, synchro_start=c.default_synchro_start, tight=tight, fadein=renpy.config.context_fadein_music, relative_volume=ctx.last_relative_volume)
|
||||
|
||||
c.last_changed = ctx.last_changed
|
||||
|
||||
|
||||
@@ -662,11 +662,19 @@ def set_audio_filter(channel, audio_filter, replace=False, duration=0.016):
|
||||
The duration to change from the current to the new filter, in seconds.
|
||||
This prevents a popping sound when changing filters.
|
||||
"""
|
||||
|
||||
replace = replace or renpy.game.after_rollback
|
||||
|
||||
if audio_filter is not None:
|
||||
audio_filter = renpy.audio.filter.to_audio_filter(audio_filter)
|
||||
|
||||
try:
|
||||
c = renpy.audio.audio.get_channel(channel)
|
||||
ctx = c.copy_context()
|
||||
|
||||
t = get_serial()
|
||||
ctx.last_changed = t
|
||||
|
||||
c.set_audio_filter(audio_filter, replace=replace, duration=duration)
|
||||
except Exception:
|
||||
if renpy.config.debug_sound:
|
||||
|
||||
@@ -74,7 +74,7 @@ cdef extern from "renpysound_core.h":
|
||||
float RPS_get_volume(int channel)
|
||||
void RPS_set_pan(int channel, float pan, float delay)
|
||||
void RPS_set_secondary_volume(int channel, float vol2, float delay)
|
||||
void RPS_replace_audio_filter(int channel, object audio_filter)
|
||||
void RPS_replace_audio_filter(int channel, object audio_filter, int primary)
|
||||
|
||||
void RPS_advance_time()
|
||||
int RPS_video_ready(int channel)
|
||||
@@ -343,15 +343,19 @@ def set_secondary_volume(channel, volume, delay):
|
||||
check_error()
|
||||
|
||||
|
||||
def replace_audio_filter(channel, audio_filter):
|
||||
def replace_audio_filter(channel, audio_filter, playing):
|
||||
"""
|
||||
Replaces the audio filter for `channel` with `audio_filter`.
|
||||
|
||||
`playing`
|
||||
If true, the filter is applied to the currently playing file and queued file. If false,
|
||||
the filter is only applied to the queued file.
|
||||
"""
|
||||
|
||||
if audio_filter is not None:
|
||||
audio_filter.prepare(get_sample_rate())
|
||||
|
||||
RPS_replace_audio_filter(channel, audio_filter)
|
||||
RPS_replace_audio_filter(channel, audio_filter, playing)
|
||||
|
||||
|
||||
def deallocate_audio_filter(audio_filter):
|
||||
|
||||
@@ -352,14 +352,14 @@ def set_secondary_volume(channel, volume, delay):
|
||||
|
||||
|
||||
@proxy_with_channel
|
||||
def replace_audio_filter(channel, audio_filter):
|
||||
def replace_audio_filter(channel, audio_filter, primary):
|
||||
"""
|
||||
Replaces the audio filter for `channel` with `audio_filter`.
|
||||
"""
|
||||
|
||||
afid = load_audio_filter(audio_filter)
|
||||
|
||||
call("replace_audio_filter", channel, afid)
|
||||
call("replace_audio_filter", channel, afid, primary)
|
||||
|
||||
|
||||
def audio_filter_constructor(f):
|
||||
|
||||
@@ -163,7 +163,6 @@ init -1500 python in build:
|
||||
("*.dll", None),
|
||||
("*.manifest", None),
|
||||
("*.keystore", None),
|
||||
( "**.rpe", None),
|
||||
( "**.rpe.py", None),
|
||||
|
||||
("update.pem", None),
|
||||
|
||||
@@ -135,15 +135,6 @@ init 1500 python hide:
|
||||
|
||||
config.emphasize_audio_volume = _vol(config.emphasize_audio_volume)
|
||||
|
||||
if not persistent._linearized_volumes:
|
||||
for k, v in _preferences.volumes.items():
|
||||
_preferences.volumes[k] = v ** 2
|
||||
|
||||
for k, v in persistent._character_volume.items():
|
||||
persistent._character_volume[k] = v ** 2
|
||||
|
||||
persistent._linearized_volumes = True
|
||||
|
||||
_apply_default_preferences()
|
||||
|
||||
error = _preferences.check()
|
||||
|
||||
@@ -1493,6 +1493,7 @@ style director_icon_action_button is director_action_button:
|
||||
|
||||
style director_icon_action_button_text is director_action_button_text:
|
||||
font "DejaVuSans.ttf"
|
||||
emoji_font None
|
||||
|
||||
style director_statement_text is director_text:
|
||||
size 20
|
||||
|
||||
@@ -720,15 +720,17 @@ init -1499 python in _renpysteam:
|
||||
|
||||
def keyboard_periodic():
|
||||
|
||||
global keyboard_mode
|
||||
global keyboard_showing
|
||||
global keyboard_primed
|
||||
global keyboard_shift
|
||||
global keyboard_baseline
|
||||
|
||||
if keyboard_mode == "always":
|
||||
keyboard_mode = "once"
|
||||
|
||||
if keyboard_mode == "never":
|
||||
return
|
||||
elif keyboard_mode == "always":
|
||||
keyboard_primed = True
|
||||
elif keyboard_mode != "once":
|
||||
raise Exception("Bad steam keyboard_mode.")
|
||||
|
||||
@@ -982,8 +984,8 @@ init -1499 python in achievement:
|
||||
if not config.enable_steam:
|
||||
return
|
||||
|
||||
# if "RENPY_NO_STEAM" in os.environ:
|
||||
# return
|
||||
if "RENPY_NO_STEAM" in os.environ:
|
||||
return
|
||||
|
||||
dll = ctypes.cdll[dll_path]
|
||||
|
||||
|
||||
@@ -211,12 +211,13 @@ let stop_playing = (c) => {
|
||||
|
||||
|
||||
if (c.playing !== null && c.playing.source !== null) {
|
||||
|
||||
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
|
||||
|
||||
try {
|
||||
c.playing.source.stop()
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
|
||||
}
|
||||
|
||||
c.playing = c.queued;
|
||||
@@ -308,6 +309,7 @@ let video_start = (c) => {
|
||||
};
|
||||
|
||||
let video_pause = (c) => {
|
||||
const p = c.playing;
|
||||
if (p.started === null) {
|
||||
return;
|
||||
}
|
||||
@@ -713,11 +715,11 @@ renpyAudio.set_pan = (channel, pan, delay) => {
|
||||
linearRampToValue(control, control.value, pan, delay);
|
||||
};
|
||||
|
||||
renpyAudio.replace_audio_filter = (channel, afid) => {
|
||||
renpyAudio.replace_audio_filter = (channel, afid, primary) => {
|
||||
let c = get_channel(channel);
|
||||
let filter = renpyAudio.getFilter(afid);
|
||||
|
||||
if (c.playing) {
|
||||
if (c.playing && primary) {
|
||||
renpyAudio.disconnectFilter(c.playing.filter, c.playing.source, c.destination);
|
||||
c.playing.filter = filter;
|
||||
renpyAudio.connectFilter(filter, c.playing.source, c.destination);
|
||||
|
||||
@@ -71,16 +71,25 @@ renpyAudio.connectFilter = function (filter, source, destination) {
|
||||
*/
|
||||
renpyAudio.disconnectFilter = function (filter, source, destination) {
|
||||
if (filter === null) {
|
||||
source.disconnect(destination);
|
||||
try {
|
||||
source.disconnect(destination);
|
||||
} catch (e) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
for (let input of filter.inputs) {
|
||||
source.disconnect(input);
|
||||
try {
|
||||
source.disconnect(input);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
for (let output of filter.outputs) {
|
||||
output.disconnect(destination);
|
||||
try {
|
||||
output.disconnect(destination);
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -130,11 +130,11 @@ screen _image_attributes():
|
||||
|
||||
for name in hidden:
|
||||
$ attributes = " ".join(renpy.get_attributes(name, layer=l))
|
||||
text _(" [name] [attributes] (hidden)")
|
||||
text _(" [name!q] [attributes!q] (hidden)")
|
||||
|
||||
for name in showing:
|
||||
$ attributes = " ".join(renpy.get_attributes(name, layer=l))
|
||||
text _(" [name] [attributes]")
|
||||
text _(" [name!q] [attributes!q]")
|
||||
|
||||
text " "
|
||||
|
||||
|
||||
+2
-2
@@ -886,7 +886,7 @@ loadable_callback = None
|
||||
|
||||
# How many frames should be drawn fast each time the screen needs to be
|
||||
# updated?
|
||||
fast_redraw_frames = 4
|
||||
fast_redraw_frames = 12
|
||||
|
||||
# The color passed to glClearColor when clearing the screen.
|
||||
gl_clear_color = "#000"
|
||||
@@ -983,7 +983,7 @@ menu_include_disabled = False
|
||||
# Should we report extraneous attributes?
|
||||
report_extraneous_attributes = True
|
||||
|
||||
# Should we play non-loooped music when skipping?
|
||||
# Should we avoid playing non-loooped music when skipping?
|
||||
skip_sounds = False
|
||||
|
||||
# Should we lint screens without parameters?
|
||||
|
||||
@@ -895,6 +895,8 @@ KEY_EVENTS = (
|
||||
|
||||
class Button(renpy.display.layout.Window):
|
||||
|
||||
_store_transform_event = True
|
||||
|
||||
keymap = { }
|
||||
action = None
|
||||
alternate = None
|
||||
@@ -2188,6 +2190,8 @@ class Bar(renpy.display.displayable.Displayable):
|
||||
to clicks on that value.
|
||||
"""
|
||||
|
||||
_store_transform_event = True
|
||||
|
||||
@property
|
||||
def _draggable(self):
|
||||
return self.focusable
|
||||
@@ -2456,8 +2460,10 @@ class Bar(renpy.display.displayable.Displayable):
|
||||
|
||||
vertical = self.style.bar_vertical
|
||||
invert = self.style.bar_invert ^ vertical
|
||||
|
||||
if invert:
|
||||
value = range - value
|
||||
old_inverted_value = value
|
||||
|
||||
grabbed = (renpy.display.focus.get_grab() is self)
|
||||
just_grabbed = False
|
||||
@@ -2467,7 +2473,7 @@ class Bar(renpy.display.displayable.Displayable):
|
||||
if not grabbed and map_event(ev, "bar_activate"):
|
||||
renpy.display.tts.speak(renpy.minstore.__("activate"))
|
||||
renpy.display.focus.set_grab(self)
|
||||
self.set_style_prefix("selected_hover_", True)
|
||||
self.set_style_prefix("hover_", True)
|
||||
just_grabbed = True
|
||||
grabbed = True
|
||||
ignore_event = True
|
||||
@@ -2527,12 +2533,16 @@ class Bar(renpy.display.displayable.Displayable):
|
||||
value = range
|
||||
|
||||
if invert:
|
||||
value = range - value
|
||||
if value == old_inverted_value: # type: ignore
|
||||
value = old_value
|
||||
else:
|
||||
value = range - value
|
||||
|
||||
|
||||
if grabbed and not just_grabbed and map_event(ev, "bar_deactivate"):
|
||||
renpy.display.tts.speak(renpy.minstore.__("deactivate"))
|
||||
self.set_style_prefix("hover_", True)
|
||||
renpy.display.focus.set_grab(None)
|
||||
self.set_style_prefix("hover_", True)
|
||||
|
||||
# Invoke rounding adjustment on bar release
|
||||
value = self.adjustment.round_value(value, release=True)
|
||||
@@ -2560,6 +2570,10 @@ class Bar(renpy.display.displayable.Displayable):
|
||||
|
||||
def set_style_prefix(self, prefix, root):
|
||||
if root:
|
||||
|
||||
if renpy.display.focus.get_grab() is self:
|
||||
prefix = "selected_" + prefix
|
||||
|
||||
super(Bar, self).set_style_prefix(prefix, root)
|
||||
|
||||
def _tts(self):
|
||||
|
||||
+21
-13
@@ -994,6 +994,14 @@ class Interface(object):
|
||||
if renpy.android:
|
||||
self.check_android_start()
|
||||
|
||||
gc.collect()
|
||||
|
||||
if gc.garbage:
|
||||
del gc.garbage[:]
|
||||
|
||||
# Kill off the presplash.
|
||||
renpy.display.presplash.end()
|
||||
|
||||
# Initialize audio.
|
||||
pygame.display.hint("SDL_APP_NAME", (renpy.config.name or "Ren'Py Game").encode("utf-8"))
|
||||
pygame.display.hint("SDL_AUDIO_DEVICE_APP_NAME", (renpy.config.name or "Ren'Py Game").encode("utf-8"))
|
||||
@@ -1011,16 +1019,8 @@ class Interface(object):
|
||||
|
||||
renpy.display.emulator.init_emulator()
|
||||
|
||||
gc.collect()
|
||||
|
||||
if gc.garbage:
|
||||
del gc.garbage[:]
|
||||
|
||||
renpy.display.render.render_ready()
|
||||
|
||||
# Kill off the presplash.
|
||||
renpy.display.presplash.end()
|
||||
|
||||
# If we are on the web browser, start preloading the browser cache.
|
||||
if renpy.emscripten and renpy.game.preferences.web_cache_preload:
|
||||
emscripten.run_script("loadCache()")
|
||||
@@ -2942,6 +2942,14 @@ class Interface(object):
|
||||
renpy.loadsave.did_autosave = False
|
||||
renpy.exports.run(renpy.config.autosave_callback)
|
||||
|
||||
# End an obsolete ongoing transition.
|
||||
if (not trans_pause) and self.ongoing_transition.get(None, None) and not self.get_ongoing_transition(None):
|
||||
self.transition.pop(None, None)
|
||||
self.ongoing_transition.pop(None, None)
|
||||
self.transition_time.pop(None, None)
|
||||
self.transition_from.pop(None, None)
|
||||
self.restart_interaction = True
|
||||
|
||||
# See if we want to restart the interaction entirely.
|
||||
if self.restart_interaction and not self.display_reset:
|
||||
return True, None
|
||||
@@ -3249,6 +3257,11 @@ class Interface(object):
|
||||
x = -1
|
||||
y = -1
|
||||
|
||||
self.event_time = end_time = get_time()
|
||||
|
||||
if ev.type in input_events:
|
||||
self.input_event_time = self.event_time
|
||||
|
||||
# This can set the event to None, to ignore it.
|
||||
ev = renpy.display.controller.event(ev)
|
||||
if not ev:
|
||||
@@ -3257,11 +3270,6 @@ class Interface(object):
|
||||
# Handle skipping.
|
||||
renpy.display.behavior.skipping(ev)
|
||||
|
||||
self.event_time = end_time = get_time()
|
||||
|
||||
if ev.type in input_events:
|
||||
self.input_event_time = self.event_time
|
||||
|
||||
try:
|
||||
|
||||
if self.touch:
|
||||
|
||||
@@ -505,18 +505,27 @@ class Displayable(renpy.object.Object):
|
||||
|
||||
return pos
|
||||
|
||||
_store_transform_event = False
|
||||
|
||||
def set_transform_event(self, event):
|
||||
"""
|
||||
Sets the transform event of this displayable to event.
|
||||
|
||||
transform_event_responder needs to be set on displayables that respond to transform events.
|
||||
|
||||
_store_transform_event should be set on displayables that store a generated transform event,
|
||||
like Button or Bar.
|
||||
"""
|
||||
|
||||
if event == self.transform_event:
|
||||
return
|
||||
if self.transform_event_responder or self._store_transform_event:
|
||||
|
||||
self.transform_event = event
|
||||
if event == self.transform_event:
|
||||
return
|
||||
|
||||
if self.transform_event_responder:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
self.transform_event = event
|
||||
|
||||
if self.transform_event_responder:
|
||||
renpy.display.render.redraw(self, 0)
|
||||
|
||||
def _handles_event(self, event):
|
||||
"""
|
||||
|
||||
@@ -94,6 +94,9 @@ class Drag(renpy.display.displayable.Displayable, renpy.revertable.RevertableObj
|
||||
has been computed, the layout properties are ignored in favor of the
|
||||
position stored inside the Drag.
|
||||
|
||||
Transforms should not be applied to a Drag directly. Instead, apply
|
||||
the transform to the child of the Drag.
|
||||
|
||||
`d`
|
||||
If present, the child of this Drag. Drags use the child style
|
||||
in preference to this, if it's not None.
|
||||
@@ -736,8 +739,12 @@ class Drag(renpy.display.displayable.Displayable, renpy.revertable.RevertableObj
|
||||
|
||||
def event(self, ev, x, y, st):
|
||||
|
||||
rv = self.child.event(ev, x, y, st)
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
if not self.is_focused():
|
||||
return self.child.event(ev, x, y, st)
|
||||
return None
|
||||
|
||||
# Mouse, in parent-relative coordinates.
|
||||
par_x = int(self.last_x + x)
|
||||
|
||||
+10
-8
@@ -468,6 +468,7 @@ def before_interact(roots):
|
||||
set_grab(None)
|
||||
set_focused(max_default_focus, None, max_default_screen)
|
||||
old_max_default = max_default
|
||||
explicit = True
|
||||
|
||||
# Try to find the current focus.
|
||||
if current is not None:
|
||||
@@ -778,7 +779,7 @@ def focus_nearest(from_x0, from_y0, from_x1, from_y1,
|
||||
focus_extreme(xmul, ymul, wmul, hmul)
|
||||
return
|
||||
|
||||
from_focus_x, from_focus_y, from_focus_w, from_focus_h = from_focus.inset_rect()
|
||||
from_focus_x, from_focus_y, from_focus_w, from_focus_h = from_rect = from_focus.inset_rect()
|
||||
|
||||
fx0 = from_focus_x + from_focus_w * from_x0
|
||||
fy0 = from_focus_y + from_focus_h * from_y0
|
||||
@@ -806,10 +807,10 @@ def focus_nearest(from_x0, from_y0, from_x1, from_y1,
|
||||
if f.x is False:
|
||||
continue
|
||||
|
||||
if not condition(from_focus, f):
|
||||
continue
|
||||
f_x, f_y, f_w, f_h = to_rect = f.inset_rect()
|
||||
|
||||
f_x, f_y, f_w, f_h = f.inset_rect()
|
||||
if not condition(from_rect, to_rect):
|
||||
continue
|
||||
|
||||
tx0 = f_x + f_w * to_x0
|
||||
ty0 = f_y + f_h * to_y0
|
||||
@@ -899,30 +900,31 @@ def key_handler(ev):
|
||||
|
||||
else:
|
||||
|
||||
|
||||
if map_event(ev, 'focus_right'):
|
||||
return focus_nearest(0.9, 0.1, 0.9, 0.9,
|
||||
0.1, 0.1, 0.1, 0.9,
|
||||
verti_line_dist,
|
||||
lambda old, new : old.x + old.w <= new.x,
|
||||
lambda old, new : old[0] + old[2] <= new[0],
|
||||
-1, 0, 0, 0)
|
||||
|
||||
if map_event(ev, 'focus_left'):
|
||||
return focus_nearest(0.1, 0.1, 0.1, 0.9,
|
||||
0.9, 0.1, 0.9, 0.9,
|
||||
verti_line_dist,
|
||||
lambda old, new : new.x + new.w <= old.x,
|
||||
lambda old, new : new[0] + new[2] <= old[0],
|
||||
1, 0, 1, 0)
|
||||
|
||||
if map_event(ev, 'focus_up'):
|
||||
return focus_nearest(0.1, 0.1, 0.9, 0.1,
|
||||
0.1, 0.9, 0.9, 0.9,
|
||||
horiz_line_dist,
|
||||
lambda old, new : new.y + new.h <= old.y,
|
||||
lambda old, new : new[1] + new[3] <= old[1],
|
||||
0, 1, 0, 1)
|
||||
|
||||
if map_event(ev, 'focus_down'):
|
||||
return focus_nearest(0.1, 0.9, 0.9, 0.9,
|
||||
0.1, 0.1, 0.9, 0.1,
|
||||
horiz_line_dist,
|
||||
lambda old, new : old.y + old.h <= new.y,
|
||||
lambda old, new : old[1] + old[3] <= new[1],
|
||||
0, -1, 0, 0)
|
||||
|
||||
+45
-53
@@ -146,8 +146,7 @@ class Cache(object):
|
||||
cache, in pixels.
|
||||
"""
|
||||
|
||||
with self.lock:
|
||||
return self.cache_size
|
||||
return self.cache_size
|
||||
|
||||
def get_current_size(self, generations):
|
||||
"""
|
||||
@@ -191,18 +190,16 @@ class Cache(object):
|
||||
# Clears out the cache.
|
||||
def clear(self):
|
||||
|
||||
self.lock.acquire()
|
||||
with self.lock:
|
||||
|
||||
self.preloads = [ ]
|
||||
self.preloads = [ ]
|
||||
|
||||
self.cache = { }
|
||||
self.cache_size = 0
|
||||
self.cache = { }
|
||||
self.cache_size = 0
|
||||
|
||||
self.first_preload_in_tick = True
|
||||
self.first_preload_in_tick = True
|
||||
|
||||
self.added.clear()
|
||||
|
||||
self.lock.release()
|
||||
self.added.clear()
|
||||
|
||||
def get_renders(self):
|
||||
"""
|
||||
@@ -211,14 +208,8 @@ class Cache(object):
|
||||
|
||||
Render = renpy.display.render.Render
|
||||
|
||||
rv = [ ]
|
||||
|
||||
with self.lock:
|
||||
for ce in self.cache.values():
|
||||
if isinstance(ce.texture, Render):
|
||||
rv.append(ce.texture)
|
||||
|
||||
return rv
|
||||
return [ ce.texture for ce in self.cache.values() if isinstance(ce.texture, Render) ]
|
||||
|
||||
# Increments time, and clears the list of images to be
|
||||
# preloaded.
|
||||
@@ -328,21 +319,23 @@ class Cache(object):
|
||||
else:
|
||||
bounds = (0, 0, w, h)
|
||||
|
||||
ce = CacheEntry(image, surf, bounds)
|
||||
|
||||
with self.lock:
|
||||
|
||||
ce = CacheEntry(image, surf, bounds)
|
||||
|
||||
if image in self.cache:
|
||||
self.kill(self.cache[image])
|
||||
old_ce = self.cache.get(image, None)
|
||||
|
||||
self.cache[image] = ce
|
||||
self.cache_size += ce.size()
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
if predict:
|
||||
renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.get_total_size() / self.cache_limit)
|
||||
else:
|
||||
renpy.display.ic_log.write("Total Miss %r", ce.what)
|
||||
if renpy.config.debug_image_cache:
|
||||
if predict:
|
||||
renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.get_total_size() / self.cache_limit)
|
||||
else:
|
||||
renpy.display.ic_log.write("Total Miss %r", ce.what)
|
||||
|
||||
if old_ce:
|
||||
self.kill(old_ce)
|
||||
|
||||
renpy.display.render.mutated_surface(ce.surf)
|
||||
|
||||
@@ -388,8 +381,7 @@ class Cache(object):
|
||||
return make_render(ce)
|
||||
|
||||
if (ce.surf is None) and (ce.texture is None):
|
||||
with self.lock:
|
||||
self.kill(ce)
|
||||
self.kill(ce)
|
||||
|
||||
return rv
|
||||
|
||||
@@ -400,8 +392,9 @@ class Cache(object):
|
||||
if ce.surf is not None:
|
||||
renpy.display.draw.mutated_surface(ce.surf)
|
||||
|
||||
self.cache_size -= ce.size()
|
||||
del self.cache[ce.what]
|
||||
with self.lock:
|
||||
self.cache_size -= ce.size()
|
||||
del self.cache[ce.what]
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
renpy.display.ic_log.write("Removed %r", ce.what)
|
||||
@@ -420,7 +413,10 @@ class Cache(object):
|
||||
# If we're outside the cache limit, we need to go and start
|
||||
# killing off some of the entries until we're back inside it.
|
||||
|
||||
for ce in sorted(self.cache.values(), key=lambda a : a.time):
|
||||
with self.lock:
|
||||
cache_values = list(self.cache.values())
|
||||
|
||||
for ce in sorted(cache_values, key=lambda a : a.time):
|
||||
|
||||
if ce.time == self.time:
|
||||
# If we're bigger than the limit, and there's nothing
|
||||
@@ -475,21 +471,19 @@ class Cache(object):
|
||||
if not isinstance(im, ImageBase):
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
if im in self.added:
|
||||
return
|
||||
|
||||
if im in self.added:
|
||||
return
|
||||
self.added.add(im)
|
||||
|
||||
self.added.add(im)
|
||||
ce = self.cache.get(im, None)
|
||||
|
||||
ce = self.cache.get(im, None)
|
||||
|
||||
if ce and ce.texture:
|
||||
ce.time = self.time
|
||||
in_cache = True
|
||||
else:
|
||||
self.preloads.append(im)
|
||||
in_cache = False
|
||||
if ce and ce.texture:
|
||||
ce.time = self.time
|
||||
in_cache = True
|
||||
else:
|
||||
self.preloads.append(im)
|
||||
in_cache = False
|
||||
|
||||
if not in_cache:
|
||||
|
||||
@@ -524,18 +518,17 @@ class Cache(object):
|
||||
|
||||
# If the size of the current generation is bigger than the
|
||||
# total cache size, stop preloading.
|
||||
with self.lock:
|
||||
|
||||
# If the cache is overfull, clean it out.
|
||||
if not self.cleanout():
|
||||
# If the cache is overfull, clean it out.
|
||||
if not self.cleanout():
|
||||
|
||||
if renpy.config.debug_image_cache:
|
||||
for i in self.preloads:
|
||||
renpy.display.ic_log.write("Overfull %r", i)
|
||||
if renpy.config.debug_image_cache:
|
||||
for i in self.preloads:
|
||||
renpy.display.ic_log.write("Overfull %r", i)
|
||||
|
||||
self.preloads = [ ]
|
||||
self.preloads = [ ]
|
||||
|
||||
break
|
||||
return
|
||||
|
||||
try:
|
||||
image = self.preloads.pop(0)
|
||||
@@ -548,8 +541,7 @@ class Cache(object):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
with self.lock:
|
||||
self.cleanout()
|
||||
self.cleanout()
|
||||
|
||||
def add_load_log(self, filename):
|
||||
|
||||
|
||||
@@ -425,6 +425,7 @@ class Frame(renpy.display.displayable.Displayable):
|
||||
return
|
||||
|
||||
rv = Render(dw, dh)
|
||||
rv.add_property("pixel_perfect", False)
|
||||
|
||||
self.draw_pattern(draw, left, top, right, bottom)
|
||||
|
||||
|
||||
@@ -132,9 +132,6 @@ class Container(renpy.display.displayable.Displayable):
|
||||
super(Container, self).__init__(**properties)
|
||||
|
||||
def set_transform_event(self, event):
|
||||
"""
|
||||
Sets the transform event of this displayable to event.
|
||||
"""
|
||||
|
||||
super(Container, self).set_transform_event(event)
|
||||
|
||||
@@ -1127,6 +1124,9 @@ class MultiBox(Container):
|
||||
first_line = False
|
||||
next_padding = 0
|
||||
|
||||
surf = render(d, rw, height - y, cst, cat)
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
line.append((d, (next_padding, 0), x, y, surf))
|
||||
line_height = max(line_height, sh)
|
||||
x += sw + next_padding
|
||||
@@ -1182,6 +1182,9 @@ class MultiBox(Container):
|
||||
first_line = False
|
||||
next_padding = 0
|
||||
|
||||
surf = render(d, width - x, rh, cst, cat)
|
||||
sw, sh = surf.get_size()
|
||||
|
||||
line.append((d, (0, next_padding), x, y, surf))
|
||||
line_width = max(line_width, sw)
|
||||
y += sh + next_padding
|
||||
|
||||
@@ -81,7 +81,7 @@ class Model(renpy.display.displayable.Displayable):
|
||||
self.size = size
|
||||
self.textures = [ ]
|
||||
|
||||
self._mesh = True
|
||||
self._mesh = None
|
||||
|
||||
self.shaders = [ ]
|
||||
self.uniforms = { }
|
||||
|
||||
@@ -206,6 +206,8 @@ def end():
|
||||
global progress_bar
|
||||
progress_bar = None
|
||||
|
||||
pygame_sdl2.display.quit()
|
||||
|
||||
|
||||
def sleep():
|
||||
"""
|
||||
|
||||
@@ -1662,6 +1662,17 @@ cdef class Render:
|
||||
else:
|
||||
self.properties[name] = value
|
||||
|
||||
def get_property(self, name, default):
|
||||
|
||||
if self.properties is None:
|
||||
return default
|
||||
|
||||
if name[:3] == "gl_":
|
||||
name = name[3:]
|
||||
|
||||
return self.properties.get(name, default)
|
||||
|
||||
|
||||
class Canvas(object):
|
||||
|
||||
def __init__(self, surf): #@DuplicatedSignature
|
||||
|
||||
+17
-4
@@ -288,10 +288,10 @@ def render_movie(channel, width, height):
|
||||
|
||||
def default_play_callback(old, new): # @UnusedVariable
|
||||
|
||||
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop, synchro_start=True)
|
||||
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop)
|
||||
|
||||
if new.mask:
|
||||
renpy.audio.music.play(new.mask, channel=new.mask_channel, loop=new.loop, synchro_start=True)
|
||||
renpy.audio.music.play(new.mask, channel=new.mask_channel, loop=new.loop)
|
||||
|
||||
# A serial number that's used to generated movie channels.
|
||||
movie_channel_serial = 0
|
||||
@@ -592,6 +592,9 @@ class Movie(renpy.display.displayable.Displayable):
|
||||
else:
|
||||
old_play = old._play
|
||||
|
||||
if (self._play is None) and (old_play is None):
|
||||
return
|
||||
|
||||
if (self._play != old_play) or renpy.config.replay_movie_sprites:
|
||||
if self._play:
|
||||
|
||||
@@ -641,29 +644,39 @@ def playing():
|
||||
return
|
||||
|
||||
|
||||
# A map from a channel to the movie playing on it in the last
|
||||
# interaction. Used to restart looping movies.
|
||||
last_channel_movie = { }
|
||||
|
||||
def update_playing():
|
||||
"""
|
||||
Calls play/stop on Movie displayables.
|
||||
"""
|
||||
|
||||
global last_channel_movie
|
||||
|
||||
old_channel_movie = renpy.game.context().movie
|
||||
|
||||
for c, m in channel_movie.items():
|
||||
|
||||
old = old_channel_movie.get(c, None)
|
||||
last = last_channel_movie.get(c, None)
|
||||
|
||||
if (c in reset_channels) and renpy.config.replay_movie_sprites:
|
||||
m.play(old)
|
||||
elif old is not m:
|
||||
m.play(old)
|
||||
elif m.loop and last is not m:
|
||||
m.play(last)
|
||||
|
||||
for c, m in old_channel_movie.items():
|
||||
for c, m in last_channel_movie.items():
|
||||
if c not in channel_movie:
|
||||
m.stop()
|
||||
|
||||
renpy.game.context().movie = dict(channel_movie)
|
||||
renpy.game.context().movie = last_channel_movie = dict(channel_movie)
|
||||
reset_channels.clear()
|
||||
|
||||
|
||||
def frequent():
|
||||
"""
|
||||
Called to update the video playback. Returns true if a video refresh is
|
||||
|
||||
+34
-29
@@ -336,12 +336,14 @@ class Viewport(renpy.display.layout.Container):
|
||||
else:
|
||||
inside = True
|
||||
|
||||
# True if the player can drag the viewoport.
|
||||
# True if the player can drag the viewport.
|
||||
draggable = self.draggable and (self.xadjustment.range or self.yadjustment.range)
|
||||
|
||||
grab = renpy.display.focus.get_grab()
|
||||
|
||||
if draggable:
|
||||
if (grab is not None) and getattr(grab, '_draggable', False) and (grab is not self):
|
||||
self.drag_position = None
|
||||
elif draggable:
|
||||
if grab is None and renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
|
||||
self.drag_position = None
|
||||
else:
|
||||
@@ -349,24 +351,22 @@ class Viewport(renpy.display.layout.Container):
|
||||
|
||||
if inside and draggable and (self.drag_position is not None) and (grab is not self):
|
||||
|
||||
focused = renpy.display.focus.get_focused()
|
||||
if ev.type == pygame.MOUSEMOTION:
|
||||
|
||||
if (focused is None) or (focused is self) or not focused._draggable:
|
||||
oldx, oldy = self.drag_position
|
||||
|
||||
if ev.type == pygame.MOUSEMOTION:
|
||||
grabbed = getattr(grab, "_draggable", False) and grab.is_focused()
|
||||
|
||||
oldx, oldy = self.drag_position
|
||||
if math.hypot(oldx - x, oldy - y) >= renpy.config.viewport_drag_radius and not grabbed:
|
||||
rv = renpy.display.focus.force_focus(self)
|
||||
renpy.display.focus.set_grab(self)
|
||||
self.drag_position = (x, y)
|
||||
self.drag_position_time = st
|
||||
self.drag_speed = (0.0, 0.0)
|
||||
grab = self
|
||||
|
||||
if math.hypot(oldx - x, oldy - y) >= renpy.config.viewport_drag_radius:
|
||||
rv = renpy.display.focus.force_focus(self)
|
||||
renpy.display.focus.set_grab(self)
|
||||
self.drag_position = (x, y)
|
||||
self.drag_position_time = st
|
||||
self.drag_speed = (0.0, 0.0)
|
||||
grab = self
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
if renpy.display.focus.get_grab() == self:
|
||||
|
||||
@@ -538,19 +538,6 @@ class Viewport(renpy.display.layout.Container):
|
||||
else:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
if inside and draggable:
|
||||
|
||||
focused = renpy.display.focus.get_focused()
|
||||
|
||||
if (focused is self) or (focused is None) or (not focused._draggable):
|
||||
if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
|
||||
self.drag_position = (x, y)
|
||||
self.drag_position_time = st
|
||||
self.drag_speed = (0.0, 0.0)
|
||||
|
||||
self.xadjustment.end_animation(instantly=True)
|
||||
self.yadjustment.end_animation(instantly=True)
|
||||
|
||||
if inside and self.edge_size and ev.type in [ pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP ]:
|
||||
|
||||
def speed(n, zero, one):
|
||||
@@ -584,11 +571,29 @@ class Viewport(renpy.display.layout.Container):
|
||||
else:
|
||||
self.edge_last_st = None
|
||||
|
||||
ignore_event = False
|
||||
|
||||
if inside and draggable:
|
||||
|
||||
if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
|
||||
|
||||
self.drag_position = (x, y)
|
||||
self.drag_position_time = st
|
||||
self.drag_speed = (0.0, 0.0)
|
||||
|
||||
self.xadjustment.end_animation(instantly=True)
|
||||
self.yadjustment.end_animation(instantly=True)
|
||||
|
||||
ignore_event = True
|
||||
|
||||
rv = super(Viewport, self).event(ev, x, y, st)
|
||||
|
||||
if rv is not None:
|
||||
return rv
|
||||
|
||||
if ignore_event:
|
||||
raise renpy.display.core.IgnoreEvent()
|
||||
|
||||
return None
|
||||
|
||||
def set_xoffset(self, offset):
|
||||
|
||||
@@ -1215,8 +1215,11 @@ cdef class GL2Draw:
|
||||
cdef unsigned char *rpp
|
||||
cdef int x, y, pitch
|
||||
|
||||
sw = render_tree.width * self.draw_per_virt
|
||||
sh = render_tree.height * self.draw_per_virt
|
||||
if render_tree is not None:
|
||||
sw = render_tree.width * self.draw_per_virt
|
||||
sh = render_tree.height * self.draw_per_virt
|
||||
else:
|
||||
sw, sh = self.drawable_size
|
||||
|
||||
full = renpy.display.pgrender.surface_unscaled((sw, sh), True)
|
||||
surf = PySurface_AsSurface(full)
|
||||
@@ -1356,6 +1359,9 @@ cdef class GL2DrawingContext:
|
||||
|
||||
sx, sy = transform.transform(0, 0)
|
||||
|
||||
sx = round(sx, 5)
|
||||
sy = round(sy, 5)
|
||||
|
||||
sx = sx * halfwidth + halfwidth
|
||||
sy = sy * halfheight + halfheight
|
||||
|
||||
|
||||
@@ -253,6 +253,7 @@ cdef class Mesh2(Mesh):
|
||||
double left, double top, double right, double bottom,
|
||||
double left_time, double right_time,
|
||||
double ascent, double descent,
|
||||
double xoffset, double yoffset
|
||||
):
|
||||
"""
|
||||
Adds a glyph to a mesh created by `text_mesh`.
|
||||
@@ -282,6 +283,10 @@ cdef class Mesh2(Mesh):
|
||||
cdef int stride = self.layout.stride
|
||||
cdef int attribute = self.points * stride
|
||||
|
||||
left -= xoffset
|
||||
right -= xoffset
|
||||
cx -= xoffset
|
||||
|
||||
self.point[point + 0].x = left
|
||||
self.point[point + 0].y = bottom
|
||||
self.attribute[attribute + 0] = tex_left
|
||||
|
||||
@@ -118,7 +118,7 @@ cdef class UniformSampler2D(Uniform):
|
||||
self.cleanup = True
|
||||
|
||||
if "anisotropic" in properties:
|
||||
if not properties.get("anisotropic", True) and renpy.display.draw.loader.max_anisotropy > 1.0:
|
||||
if not properties.get("anisotropic", True) and renpy.display.draw.texture_loader.max_anisotropy > 1.0:
|
||||
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, 1.0)
|
||||
self.cleanup = True
|
||||
|
||||
@@ -141,8 +141,8 @@ cdef class UniformSampler2D(Uniform):
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
||||
|
||||
if "anisotropic" in properties:
|
||||
if not properties.get("anisotropic", True) and renpy.display.draw.loader.max_anisotropy > 1.0:
|
||||
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, renpy.display.draw.loader.max_anisotropy)
|
||||
if not properties.get("anisotropic", True) and renpy.display.draw.texture_loader.max_anisotropy > 1.0:
|
||||
glTexParameterf(GL_TEXTURE_2D, TEXTURE_MAX_ANISOTROPY_EXT, renpy.display.draw.texture_loader.max_anisotropy)
|
||||
|
||||
self.last_data = None
|
||||
|
||||
|
||||
+5
-1
@@ -863,7 +863,11 @@ class Live2D(renpy.display.displayable.Displayable):
|
||||
state.old_expressions = [ (name, shown, hidden) for (name, shown, hidden) in state.old_expressions if (now - hidden) < common.all_expressions[name].fadeout ]
|
||||
|
||||
# Determine the list of expressions that are being shown by this displayable.
|
||||
expressions = list(self.used_nonexclusive) # type: ignore
|
||||
if self.used_nonexclusive is None:
|
||||
expressions = [ ]
|
||||
else:
|
||||
expressions = list(self.used_nonexclusive) # type: ignore
|
||||
|
||||
if self.expression:
|
||||
expressions.append(self.expression)
|
||||
|
||||
|
||||
+12
-3
@@ -1439,7 +1439,7 @@ class Lexer(object):
|
||||
|
||||
return self.filename, self.number
|
||||
|
||||
def require(self, thing, name=None):
|
||||
def require(self, thing, name=None, **kwargs):
|
||||
"""
|
||||
Tries to parse thing, and reports an error if it cannot be done.
|
||||
|
||||
@@ -1452,10 +1452,14 @@ class Lexer(object):
|
||||
name = name or thing
|
||||
rv = self.match(thing)
|
||||
else:
|
||||
name = name or thing.__func__.__name__
|
||||
rv = thing()
|
||||
rv = thing(**kwargs)
|
||||
|
||||
if rv is None:
|
||||
if isinstance(thing, basestring):
|
||||
name = name or thing
|
||||
else:
|
||||
name = name or thing.__func__.__name__
|
||||
|
||||
self.error("expected '%s' not found." % name)
|
||||
|
||||
return rv
|
||||
@@ -1587,6 +1591,11 @@ def ren_py_to_rpy(text, filename):
|
||||
"""
|
||||
|
||||
lines = text.splitlines()
|
||||
|
||||
# Skip the BOM, if any.
|
||||
if lines and lines[0][:1] == u'\ufeff':
|
||||
lines[0] = lines[0][1:]
|
||||
|
||||
result = [ ]
|
||||
|
||||
# The prefix prepended to Python lines.
|
||||
|
||||
+20
-5
@@ -71,17 +71,27 @@ def get_path(fn):
|
||||
|
||||
apks = [ ]
|
||||
game_apks = [ ]
|
||||
split_apks = [ ]
|
||||
|
||||
if renpy.android:
|
||||
import android.apk # type: ignore
|
||||
|
||||
packs = [os.environ[i] for i in ["ANDROID_PACK_FF" + str(j+1) for j in range(4)] if i in os.environ and os.environ[i].endswith(".apk")]
|
||||
|
||||
if renpy.config.renpy_base == renpy.config.basedir:
|
||||
# Read the game data from the APK.
|
||||
# Read the game data from the APKs.
|
||||
|
||||
apks.append(android.apk.APK(prefix='assets/x-game/'))
|
||||
game_apks.append(apks[0])
|
||||
game_apks.append(apks[-1])
|
||||
for i in packs:
|
||||
apks.append(android.apk.APK(apk=i, prefix='assets/game/'))
|
||||
game_apks.append(apks[-1])
|
||||
split_apks.append(apks[-1])
|
||||
|
||||
apks.append(android.apk.APK(prefix='assets/x-renpy/x-common/'))
|
||||
for i in packs:
|
||||
apks.append(android.apk.APK(apk=i, prefix='assets/renpy/common/'))
|
||||
split_apks.append(apks[-1])
|
||||
|
||||
|
||||
# Files on disk should be checked before archives. Otherwise, among
|
||||
@@ -360,7 +370,8 @@ def scandirfiles_from_apk(add, seen):
|
||||
|
||||
# Strip off the "x-" in front of each filename, which is there
|
||||
# to ensure that aapt actually includes every file.
|
||||
f = "/".join(i[2:] for i in f.split("/"))
|
||||
if apk not in split_apks:
|
||||
f = "/".join(i[2:] for i in f.split("/"))
|
||||
|
||||
add(None, f, files, seen)
|
||||
|
||||
@@ -579,7 +590,9 @@ def load_from_apk(name):
|
||||
"""
|
||||
|
||||
for apk in apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
prefixed_name = name
|
||||
if apk not in split_apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
|
||||
try:
|
||||
return apk.open(prefixed_name)
|
||||
@@ -691,7 +704,9 @@ def loadable_core(name):
|
||||
pass
|
||||
|
||||
for apk in apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
prefixed_name = name
|
||||
if apk not in split_apks:
|
||||
prefixed_name = "/".join("x-" + i for i in name.split("/"))
|
||||
if prefixed_name in apk.info:
|
||||
loadable_cache[name] = True
|
||||
return True
|
||||
|
||||
+12
-6
@@ -98,20 +98,20 @@ def parse_image_name(l, string=False, nodash=False):
|
||||
return tuple(rv)
|
||||
|
||||
|
||||
def parse_simple_expression_list(l):
|
||||
def parse_simple_expression_list(l, image=False):
|
||||
"""
|
||||
This parses a comma-separated list of simple_expressions, and
|
||||
returns a list of strings. It requires at least one
|
||||
simple_expression be present.
|
||||
"""
|
||||
|
||||
rv = [ l.require(l.simple_expression) ]
|
||||
rv = [ l.require(l.simple_expression, image=image) ]
|
||||
|
||||
while True:
|
||||
if not l.match(','):
|
||||
break
|
||||
|
||||
e = l.simple_expression()
|
||||
e = l.simple_expression(image=image)
|
||||
|
||||
if not e:
|
||||
break
|
||||
@@ -133,7 +133,7 @@ def parse_image_specifier(l):
|
||||
behind = [ ]
|
||||
|
||||
if l.keyword("expression") or l.keyword("image"):
|
||||
expression = l.require(l.simple_expression)
|
||||
expression = l.require(l.simple_expression, image=True)
|
||||
image_name = (expression.strip(),)
|
||||
else:
|
||||
image_name = parse_image_name(l, True)
|
||||
@@ -154,7 +154,7 @@ def parse_image_specifier(l):
|
||||
if at_list:
|
||||
l.error("multiple at clauses are prohibited.")
|
||||
else:
|
||||
at_list = parse_simple_expression_list(l)
|
||||
at_list = parse_simple_expression_list(l, image=True)
|
||||
|
||||
continue
|
||||
|
||||
@@ -172,7 +172,7 @@ def parse_image_specifier(l):
|
||||
if zorder is not None:
|
||||
l.error("multiple zorder clauses are prohibited.")
|
||||
else:
|
||||
zorder = l.require(l.simple_expression)
|
||||
zorder = l.require(l.simple_expression, image=True)
|
||||
|
||||
continue
|
||||
|
||||
@@ -271,6 +271,12 @@ def parse_menu(stmtl, loc, arguments):
|
||||
|
||||
say_ast = finish_say(l, l.get_location(), who, what, attributes, temporary_attributes, interact=False)
|
||||
|
||||
if isinstance(say_ast, list):
|
||||
if len(say_ast) == 1:
|
||||
say_ast = say_ast[0]
|
||||
else:
|
||||
l.error("Monologue mode cannot be used in a menu.")
|
||||
|
||||
l.expect_eol()
|
||||
l.expect_noblock("say menuitem")
|
||||
continue
|
||||
|
||||
@@ -1036,8 +1036,8 @@ cdef class HBFont:
|
||||
x = <int> (glyph.x + xo + glyph.x_offset)
|
||||
y = <int> (glyph.y + yo + glyph.y_offset)
|
||||
|
||||
underline_x = x - glyph.delta_x_adjustment
|
||||
underline_end = x + <int> (glyph.advance + expand + .9999)
|
||||
underline_x = <int> (glyph.x + xo - glyph.delta_x_adjustment)
|
||||
underline_end = <int> (glyph.x + xo + glyph.advance + expand + .9999)
|
||||
|
||||
cache = self.get_glyph(glyph.glyph)
|
||||
|
||||
|
||||
+87
-23
@@ -1406,7 +1406,8 @@ class Layout(object):
|
||||
if value[0] in "+-":
|
||||
push().size += int(value)
|
||||
elif value[0] == "*":
|
||||
push().size = int(float(value[1:]) * push().size)
|
||||
ts = push()
|
||||
ts.size = int(float(value[1:]) * ts.size)
|
||||
else:
|
||||
push().size = int(value)
|
||||
|
||||
@@ -1735,6 +1736,13 @@ class Layout(object):
|
||||
The text shader.
|
||||
"""
|
||||
|
||||
first_shader = ts
|
||||
|
||||
for l in lines:
|
||||
for g in l.glyphs:
|
||||
first_shader = g.shader
|
||||
break
|
||||
|
||||
tw, th = tex.get_size()
|
||||
|
||||
if lines:
|
||||
@@ -1745,12 +1753,17 @@ class Layout(object):
|
||||
# The number of glyphs in the mesh.
|
||||
n_glyphs = sum(len(l.glyphs) for l in lines)
|
||||
|
||||
mesh = renpy.gl2.gl2mesh2.Mesh2.text_mesh(n_glyphs)
|
||||
mesh = renpy.gl2.gl2mesh2.Mesh2.text_mesh(n_glyphs + 2 * len(lines))
|
||||
|
||||
# The y coordinate of the top line.
|
||||
|
||||
top = 0
|
||||
|
||||
# The index of the last glyph to be shown.
|
||||
last_index = 0
|
||||
|
||||
# The time of the last glyph to be shown.
|
||||
last_time = 0.0
|
||||
|
||||
for line in lines:
|
||||
|
||||
# Line bottom is the y coordinate of the bottom line.
|
||||
@@ -1766,26 +1779,48 @@ class Layout(object):
|
||||
first_glyph = None
|
||||
last_glyph = None
|
||||
|
||||
left = 0
|
||||
|
||||
if first_glyph:
|
||||
right = first_glyph.x + self.add_left
|
||||
else:
|
||||
right = tw
|
||||
|
||||
# Generate a psuedo-glyph for the text to the left of the line.
|
||||
# These pseudo-glyphs are used to make sure that outlines of lines above and below
|
||||
# are displayed.
|
||||
|
||||
if outline:
|
||||
if right > 0 and (ts == first_shader):
|
||||
|
||||
cx = 0 + right / 2
|
||||
cy = outline + line.baseline
|
||||
|
||||
mesh.add_glyph(
|
||||
tw, th,
|
||||
cx, cy,
|
||||
last_index,
|
||||
left, top, right, bottom,
|
||||
last_time, last_time,
|
||||
line.baseline, line.height - line.baseline,
|
||||
self.add_left, self.add_top,
|
||||
)
|
||||
|
||||
# Generate the actual glyphs.
|
||||
|
||||
for g in line.glyphs:
|
||||
|
||||
left = right
|
||||
|
||||
if g.time == -1:
|
||||
continue
|
||||
|
||||
# Check that this is the right shader to use.
|
||||
if (g.shader is not ts) and (g.shader != ts):
|
||||
continue
|
||||
|
||||
# The x-coordinate of the left edge of the glyph.
|
||||
if g is first_glyph:
|
||||
left = g.x - self.add_left
|
||||
else:
|
||||
left = g.x + outline
|
||||
|
||||
# The x-coordinate of the right edge of the glyph.
|
||||
if g is last_glyph:
|
||||
right = g.x + g.advance + outline * 2 + self.add_right
|
||||
right = g.x + g.advance + outline * 2 + self.add_left + self.add_right
|
||||
else:
|
||||
right = g.x + g.advance + outline
|
||||
right = g.x + g.advance + outline + self.add_left
|
||||
|
||||
if left < 0:
|
||||
left = 0
|
||||
@@ -1794,7 +1829,7 @@ class Layout(object):
|
||||
|
||||
# The center coordinates of the glyph. These aren't the
|
||||
# actual center, but the center of the baseline.
|
||||
cx = g.x + g.advance / 2
|
||||
cx = g.x + g.advance / 2 + self.add_left
|
||||
cy = outline + line.baseline
|
||||
|
||||
duration = g.duration
|
||||
@@ -1808,14 +1843,43 @@ class Layout(object):
|
||||
left_time = g.time - duration
|
||||
right_time = g.time
|
||||
|
||||
mesh.add_glyph(
|
||||
tw, th,
|
||||
cx, cy,
|
||||
g.index,
|
||||
left, top, right, bottom,
|
||||
left_time, right_time,
|
||||
g.ascent, g.descent,
|
||||
)
|
||||
|
||||
# Check that this is the right shader to use.
|
||||
if (g.shader is ts) or (g.shader == ts):
|
||||
|
||||
mesh.add_glyph(
|
||||
tw, th,
|
||||
cx, cy,
|
||||
g.index,
|
||||
left, top, right, bottom,
|
||||
left_time, right_time,
|
||||
g.ascent, g.descent,
|
||||
self.add_left, self.add_top,
|
||||
)
|
||||
|
||||
last_time = g.time
|
||||
last_index = g.index
|
||||
|
||||
# Handle the empty space to the right of the last glyph.
|
||||
if outline:
|
||||
|
||||
if right < tw and (ts == first_shader):
|
||||
|
||||
left = right
|
||||
right = tw
|
||||
|
||||
cx = left + right / 2
|
||||
cy = outline + line.baseline
|
||||
|
||||
mesh.add_glyph(
|
||||
tw, th,
|
||||
cx, cy,
|
||||
last_index,
|
||||
left, top, right, bottom,
|
||||
last_time, last_time,
|
||||
line.baseline, line.height - line.baseline,
|
||||
self.add_left, self.add_top,
|
||||
)
|
||||
|
||||
top = bottom
|
||||
|
||||
|
||||
+2
-2
@@ -51,8 +51,8 @@ class Version(object):
|
||||
|
||||
Version("main", 3, "8.4.0", "TBD")
|
||||
|
||||
Version("fix", 3, "8.3.1", "Second Star to the Right")
|
||||
Version("fix", 2, "7.8.1", "Straight on Till Morning")
|
||||
Version("fix", 3, "8.3.3", "Second Star to the Right")
|
||||
Version("fix", 2, "7.8.3", "Straight on Till Morning")
|
||||
|
||||
|
||||
def make_dict(branch, suffix="00000000", official=False, nightly=False):
|
||||
|
||||
@@ -51,7 +51,9 @@ For example, the above behaviour, but written as a CDS::
|
||||
|
||||
def lint_random(parsed_object):
|
||||
for i in parsed_object:
|
||||
renpy.error(renpy.check_text_tags(i.what))
|
||||
check = renpy.check_text_tags(i.block[0].what)
|
||||
if check:
|
||||
renpy.error(check)
|
||||
|
||||
|
||||
renpy.register_statement(
|
||||
|
||||
@@ -4,6 +4,92 @@ Changelog (Ren'Py 7.x-)
|
||||
|
||||
*There is also a list of* :doc:`incompatible changes <incompatible>`
|
||||
|
||||
|
||||
.. _renpy-8.3.3:
|
||||
.. _renpy-7.8.3:
|
||||
|
||||
Fixes
|
||||
-----
|
||||
|
||||
Changes to audio filters take place immediately after reload.
|
||||
|
||||
:var:`config.skip_sounds` now works as documented.
|
||||
|
||||
:class:`Model` now creates meshes as described in the documentation when no textures are supplied.
|
||||
|
||||
The image attributes screen now quotes image and displayable names.
|
||||
|
||||
An issue with rollback not restarting music has been fixed.
|
||||
|
||||
Underlines and strikethroughs will not be broken when drawn using harfbuzz-based text shaping.
|
||||
|
||||
Ren'Py now ensures that IMEs are activated on the primary window when the game starts, rather than on
|
||||
a presplash window.
|
||||
|
||||
Bars no longer lose the ``selected_hover`` prefix when adjusted using keyboard or game controller.
|
||||
|
||||
Rounding errors during pixel perfect text positioning have been fixed. These errors could cause text to
|
||||
jump 1 pixel during dissolves.
|
||||
|
||||
The rarely-used ``gl_anisotropic`` transform property now works.
|
||||
|
||||
The :propref:`keyboard_focus_insets` property now works as documented.
|
||||
|
||||
A rounding issue that could cause :propref:`bar_invert` from working has been fixed.
|
||||
|
||||
Ren'Py will render a displayable a second time if :propref:`box_wrap` is True, to ensure that the displayable
|
||||
is offered the correct amount of space when wrapped to a second line. In rare cases, this could change layout.
|
||||
|
||||
Controller events can now cause Ren'Py to focus a displayable with `default_focus` set. Previously, these
|
||||
events weren't considered inputs by the focus system.
|
||||
|
||||
There have been a number of fixes to the way Ren'Py handles dragging a viewport filled with buttons.
|
||||
|
||||
A drag may now contain a draggable viewport, allowing a window to be more directly emulated.
|
||||
|
||||
Other Changes
|
||||
-------------
|
||||
|
||||
Android bundles now use install-time assets packs, rather than fast-follow packs, to ensure that all assets
|
||||
are available when the game is run.
|
||||
|
||||
The "always" option to _renpysteam.keyboard_mode is no longer supported. If given, the "once" mode is
|
||||
used, requiring the player to explicitly request the Steam Deck keyboard when required.
|
||||
|
||||
The number of frames that Ren'Py passes through the rendering pipeline before switching to powersave
|
||||
mode has been increased to 12, to ensure that frames make it through compositors in a timely manner.
|
||||
|
||||
Ren'Py locks the image cache less, which should prevent some frame drops when loading images.
|
||||
|
||||
Synchronized start of audio/video no longer occurs on movie channels unless explicitly requested.
|
||||
|
||||
When rolling back to a point where a looping :class:`Movie` was showing, the looping movie will be played again
|
||||
if it had been stopped.
|
||||
|
||||
A :class:`Movie` will only stop movies that it has played, rather than any movie on the associated channel.
|
||||
|
||||
When :func:`renpy.set_audio_filter` is called with `immediate` false, the filter will be applied when
|
||||
the queued file is played, rather than at some indeterminate time in the future.
|
||||
|
||||
The :class:`Frame` displayable is no longer adjusted to be pixel perfect, preventing visual glitches.
|
||||
|
||||
When using text shaders to display text with outlines, Ren'Py will create pseudo-glyphs. These pseudo-glyphs
|
||||
cover the start and end of each line, and are used to ensure the outlines will be shown.
|
||||
|
||||
|
||||
.. _renpy-8.3.2:
|
||||
.. _renpy-7.8.2:
|
||||
|
||||
8.3.2 / 7.8.2
|
||||
=============
|
||||
|
||||
Fixes
|
||||
-----
|
||||
|
||||
Fixed a build issue with 8.3.1 and 7.8.1 that prevented the Android version of Ren'Py from starting properly,
|
||||
making games unplayable.
|
||||
|
||||
|
||||
.. _renpy-8.3.1:
|
||||
.. _renpy-7.8.1:
|
||||
|
||||
@@ -13,6 +99,9 @@ Changelog (Ren'Py 7.x-)
|
||||
Fixes
|
||||
-----
|
||||
|
||||
Image keywords (``zorder``, ``behind``, ``at``, ``onlayer``, and ``transform``) may not occur in an expression,
|
||||
like a list after ``at``.
|
||||
|
||||
Using local shader variables by name in {shader} tags now works.
|
||||
|
||||
Textshaders now work with very large sizes and numbers of characters.
|
||||
|
||||
@@ -36,7 +36,7 @@ The callback is called with at the keyword arguments:
|
||||
This is true if the dialogue causes an interaction to occur.
|
||||
|
||||
`type`
|
||||
The type of character (e.g. "nvl", "adv", "balloon").
|
||||
The type of character (e.g. "nvl", "adv", "bubble").
|
||||
|
||||
`what`
|
||||
The text that is going to be supplied to the what displayable.
|
||||
|
||||
@@ -769,7 +769,7 @@ Media (Music, Sound, and Video)
|
||||
|
||||
.. var:: config.skip_sounds = False
|
||||
|
||||
If False, non-looping audio will not be played when Ren'Py is
|
||||
If True, non-looping audio will not be played when Ren'Py is
|
||||
skipping.
|
||||
|
||||
.. var:: config.sound = True
|
||||
|
||||
@@ -19,7 +19,9 @@ the omission in future versions.
|
||||
* Ajkahn96
|
||||
* Aleema
|
||||
* Alessio
|
||||
* Aleksandar Belic Aleksanchez
|
||||
* Alex
|
||||
* Alexandre Detiste
|
||||
* Alexandre Tranchant
|
||||
* Alisha Taylor
|
||||
* Altskop
|
||||
@@ -31,6 +33,7 @@ the omission in future versions.
|
||||
* Arowana-vx
|
||||
* Asfdfdfd
|
||||
* Auro-Cyanide
|
||||
* Avonder
|
||||
* Awakening
|
||||
* Ayowel
|
||||
* Baekansi
|
||||
@@ -39,12 +42,14 @@ the omission in future versions.
|
||||
* Ben Wright
|
||||
* BlackDragonHunt/Mangagamer
|
||||
* Brainos
|
||||
* Brimbel
|
||||
* Bryan Tsang
|
||||
* Carl
|
||||
* Capntrips
|
||||
* Caryoscelus
|
||||
* CensoredUsername (C)
|
||||
* Charles Surett
|
||||
* Chengtian He
|
||||
* Cherie Davidson
|
||||
* Chrisclone
|
||||
* Chris Mear
|
||||
@@ -53,6 +58,7 @@ the omission in future versions.
|
||||
* Clinton Nguyen
|
||||
* CobaltCore
|
||||
* CodePsy
|
||||
* Craig de Stigter
|
||||
* Craig P. Donson
|
||||
* 被诅咒的章鱼
|
||||
* DaFool
|
||||
@@ -101,6 +107,7 @@ the omission in future versions.
|
||||
* GNCanva
|
||||
* Gouvernathor
|
||||
* Gratusfr
|
||||
* Gregor Riepl
|
||||
* Gregory Pease
|
||||
* Grey
|
||||
* Gustavo Carvalho
|
||||
@@ -152,6 +159,7 @@ the omission in future versions.
|
||||
* Konstantin Mozheyko
|
||||
* Konstantin Nikolayev
|
||||
* KorDen
|
||||
* Kuro
|
||||
* Kuroonehalf
|
||||
* Kyouryuukunn
|
||||
* Lapalissiano
|
||||
@@ -166,13 +174,17 @@ the omission in future versions.
|
||||
* Liu Wenyuan
|
||||
* Lore
|
||||
* Lucas Ramage
|
||||
* Luejerry
|
||||
* M-From-Space
|
||||
* Maciej Katafiasz
|
||||
* Maissara Moustafa
|
||||
* Mal Graty
|
||||
* Marcel
|
||||
* Mark
|
||||
* Markus Koschany
|
||||
* Mason Chou
|
||||
* Matias B.
|
||||
* Matěj Račinský
|
||||
* Matt George
|
||||
* Matthew Vimislik
|
||||
* Max le Fou
|
||||
@@ -197,9 +209,11 @@ the omission in future versions.
|
||||
* NoJoker
|
||||
* Nolanlemahn
|
||||
* Noriverwater
|
||||
* Npckc
|
||||
* Numerlor
|
||||
* Nxcrft
|
||||
* Nyaatrap
|
||||
* OctoSpacc
|
||||
* OleSTEEP
|
||||
* Oscar Six
|
||||
* Oshi-Shinobu
|
||||
@@ -208,6 +222,7 @@ the omission in future versions.
|
||||
* Paul Morio
|
||||
* Pavel Langwell
|
||||
* Peter DeVita
|
||||
* Petr Abdulin
|
||||
* Philat
|
||||
* Pionere
|
||||
* Piroshki
|
||||
@@ -243,6 +258,7 @@ the omission in future versions.
|
||||
* Siege-Wizard
|
||||
* SleepKirby
|
||||
* Spiky Caterpillar
|
||||
* Sunrise Sarsaparilla
|
||||
* Susnux
|
||||
* Sylvain Beucler
|
||||
* Symegac
|
||||
@@ -255,6 +271,8 @@ the omission in future versions.
|
||||
* Totally a booplicate
|
||||
* Vadim Karpenko
|
||||
* Valery Iwanofu
|
||||
* Veydzh3r
|
||||
* Viktoras Agejevas
|
||||
* Viliam Búr
|
||||
* Vladya
|
||||
* Vollschauer
|
||||
|
||||
@@ -148,7 +148,7 @@ resolution graphics.
|
||||
|
||||
Oversampling is automatically enabled if the image ends with an '@' followed
|
||||
by a number, before the extension. For example, :file:`eileen happy@2.png` is
|
||||
2x oversampled, and :file:`eileen happy@3x.png` will be 3x oversampled. Oversampling
|
||||
2x oversampled, and :file:`eileen happy@3.png` will be 3x oversampled. Oversampling
|
||||
can also be enabled by giving the `oversample` keyword argument to :func:`Image`.
|
||||
|
||||
A directory can also specify how much to oversample images inside it. For example,
|
||||
|
||||
@@ -110,7 +110,7 @@ as of version 8.1.0 is as follows::
|
||||
help = [ 'K_F1', 'meta_shift_/' ],
|
||||
choose_renderer = ['alt_K_g', 'shift_K_g' ],
|
||||
progress_screen = [ 'alt_shift_K_p', 'meta_shift_K_p', 'K_F2' ],
|
||||
accessibility = [ 'K_a' ],
|
||||
accessibility = [ 'shift_K_a' ],
|
||||
|
||||
# Accessibility.
|
||||
self_voicing = [ 'alt_K_v', 'K_v' ],
|
||||
|
||||
@@ -373,7 +373,7 @@ Tags that apply to all text are:
|
||||
The shader tag applies a text shader to a section of text.
|
||||
The argument is the name of the shader to apply. ::
|
||||
|
||||
"This text is {shader=jitter:speed=3}jittery{/shader}."
|
||||
"This text is {shader=jitter:u__jitter=1.0, 3.0}jittery{/shader}."
|
||||
|
||||
See :doc:`textshaders` for more information.
|
||||
|
||||
|
||||
@@ -234,6 +234,12 @@ locations, but these are passed to the shader as different vertices.
|
||||
The rectangle containing the glyph, in drawable pixels. This is a vec4 with the x, y, width, and height of the rectangle,
|
||||
in drawable pixels. This can be converted to texture coordinates by dividing it by ``res0``.
|
||||
|
||||
Pseudo-Glyphs
|
||||
-------------
|
||||
|
||||
When drawing outlined text, Ren'Py will creates pseudo-glyphs that cover the start and end of each line. If a line
|
||||
is blank, one pseudo-glyph is created covering the whole line. These pseudo-glyphs are necessary to cover areas
|
||||
where outlines may extend into a line above or below the current line.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Reference in New Issue
Block a user