From 718936110b9511631fa1f4396be992752bf8b719 Mon Sep 17 00:00:00 2001 From: Alex Xu Date: Thu, 22 Aug 2013 22:45:26 -0400 Subject: include renpy --- unrpyc/renpy/display/core.py | 2463 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2463 insertions(+) create mode 100644 unrpyc/renpy/display/core.py (limited to 'unrpyc/renpy/display/core.py') diff --git a/unrpyc/renpy/display/core.py b/unrpyc/renpy/display/core.py new file mode 100644 index 0000000..4731df0 --- /dev/null +++ b/unrpyc/renpy/display/core.py @@ -0,0 +1,2463 @@ +# Copyright 2004-2013 Tom Rothamel +# +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# This file contains code for initializing and managing the display +# window. + +import renpy.display +import renpy.audio +import renpy.text + +import pygame #@UnusedImport + +import sys +import os +import time +import io +import threading + +try: + import android #@UnresolvedImport @UnusedImport + import android.sound #@UnresolvedImport +except: + android = None + +# Is the cpu idle enough to do other things? +cpu_idle = threading.Event() +cpu_idle.clear() + +# Need to be +4, so we don't interfere with FFMPEG's events. +TIMEEVENT = pygame.USEREVENT + 4 +PERIODIC = pygame.USEREVENT + 5 +JOYEVENT = pygame.USEREVENT + 6 +REDRAW = pygame.USEREVENT + 7 + +# All events except for TIMEEVENT and REDRAW +ALL_EVENTS = [ i for i in range(0, REDRAW + 1) if i != TIMEEVENT and i != REDRAW ] + +# The number of msec between periodic events. +PERIODIC_INTERVAL = 50 + +# Time management. +time_base = None + +def init_time(): + global time_base + time_base = time.time() - pygame.time.get_ticks() / 1000.0 + +def get_time(): + return time_base + pygame.time.get_ticks() / 1000.0 + + +def displayable_by_tag(layer, tag): + """ + Get the displayable on the given layer with the given tag. + """ + + return renpy.game.context().scene_lists.get_displayable_by_tag(layer, tag) + +class IgnoreEvent(Exception): + """ + Exception that is raised when we want to ignore an event, but + also don't want to return anything. + """ + + pass + +class EndInteraction(Exception): + """ + Exception that can be raised (for example, during the render method of + a displayable) to end the current interaction immediately. + """ + + def __init__(self, value): + self.value = value + +class absolute(float): + """ + This represents an absolute float coordinate. + """ + __slots__ = [ ] + + +class Displayable(renpy.object.Object): + """ + The base class for every object in Ren'Py that can be + displayed to the screen. + + Drawables will be serialized to a savegame file. Therefore, they + shouldn't store non-serializable things (like pygame surfaces) in + their fields. + """ + + # Some invariants about method call order: + # + # per_interact is called before render. + # render is called before event. + # + # get_placement can be called at any time, so can't + # assume anything. + + activated = False + focusable = False + full_focus_name = None + role = '' + + # The event we'll pass on to our parent transform. + transform_event = None + + # Can we change our look in response to transform_events? + transform_event_responder = False + + def __init__(self, focus=None, default=False, style='default', **properties): # W0231 + self.style = renpy.style.Style(style, properties, heavy=True) + self.focus_name = focus + self.default = default + + def find_focusable(self, callback, focus_name): + + focus_name = self.focus_name or focus_name + + if self.focusable: + callback(self, focus_name) + + for i in self.visit(): + if i is None: + continue + + i.find_focusable(callback, focus_name) + + + def focus(self, default=False): + """ + Called to indicate that this widget has the focus. + """ + + if not self.activated: + self.set_style_prefix(self.role + "hover_", True) + + if not default and not self.activated: + if self.style.sound: + renpy.audio.music.play(self.style.sound, channel="sound") + + def unfocus(self, default=False): + """ + Called to indicate that this widget has become unfocused. + """ + + if not self.activated: + self.set_style_prefix(self.role + "idle_", True) + + def is_focused(self): + + if renpy.display.focus.grab and renpy.display.focus.grab is not self: + return + + return renpy.game.context().scene_lists.focused is self + + def set_style_prefix(self, prefix, root): + """ + Called to set the style prefix of this widget and its child + widgets, if any. + + `root` - True if this is the root of a style tree, False if this + has been passed on to a child. + """ + + if prefix == self.style.prefix: + return + + self.style.set_prefix(prefix) + renpy.display.render.redraw(self, 0) + + def parameterize(self, name, parameters): + """ + Called to parameterize this. By default, we don't take any + parameters. + """ + + if parameters: + raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" % + (' '.join(name), ' '.join(parameters))) + + return self + + def render(self, width, height, st, at): + """ + Called to display this displayable. This is called with width + and height parameters, which give the largest width and height + that this drawable can be drawn to without overflowing some + bounding box. It's also given two times. It returns a Surface + that is the current image of this drawable. + + @param st: The time since this widget was first shown, in seconds. + @param at: The time since a similarly named widget was first shown, + in seconds. + """ + + assert False, "Draw not implemented." + + def event(self, ev, x, y, st): + """ + Called to report than an event has occured. Ev is the raw + pygame event object representing that event. If the event + involves the mouse, x and y are the translation of the event + into the coordinates of this displayable. st is the time this + widget has been shown for. + + @returns A value that should be returned from Interact, or None if + no value is appropriate. + """ + + return None + + def get_placement(self): + """ + Returns a style object containing placement information for + this Displayable. Children are expected to overload this + to return something more sensible. + """ + + return self.style.get_placement() + + def visit_all(self, callback): + """ + Calls the callback on this displayable and all children of this + displayable. + """ + + for d in self.visit(): + if not d: + continue + d.visit_all(callback) + + callback(self) + + def visit(self): + """ + Called to ask the displayable to return a list of its children + (including children taken from styles). For convenience, this + list may also include None values. + """ + + return [ ] + + def per_interact(self): + """ + Called once per widget per interaction. + """ + + return None + + def predict_one(self): + """ + Called to ask this displayable to call the callback with all + the images it may want to load. + """ + + return + + def predict_one_action(self): + """ + Called to ask this displayable to cause image prediction + to occur for images that may be loaded by its actions. + """ + + return + + def place(self, dest, x, y, width, height, surf, main=True): + """ + This draws this Displayable onto a destination surface, using + the placement style information returned by this object's + get_placement() method. + + @param dest: The surface that this displayable will be drawn + on. + + @param x: The minimum x coordinate on this surface that this + Displayable will be drawn to. + + @param y: The minimum y coordinate on this surface that this + displayable will be drawn to. + + @param width: The width of the area allocated to this + Displayable. + + @param height: The height of the area allocated to this + Displayable. + + @param surf: The surface returned by a previous call to + self.render(). + """ + + xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.get_placement() + + if xpos is None: + xpos = 0 + if ypos is None: + ypos = 0 + if xanchor is None: + xanchor = 0 + if yanchor is None: + yanchor = 0 + if xoffset is None: + xoffset = 0 + if yoffset is None: + yoffset = 0 + + # We need to use type, since isinstance(absolute(0), float). + if xpos.__class__ is float: + xpos *= width + + if xanchor.__class__ is float: + xanchor *= surf.width + + xpos += x + xoffset - xanchor + + # y + + if ypos.__class__ is float: + ypos *= height + + if yanchor.__class__ is float: + yanchor *= surf.height + + ypos += y + yoffset - yanchor + + if dest is not None: + if subpixel: + dest.subpixel_blit(surf, (xpos, ypos), main, main, None) + else: + dest.blit(surf, (xpos, ypos), main, main, None) + + return xpos, ypos + + def set_transform_event(self, event): + """ + Sets the transform event of this displayable to event. + """ + + if event == self.transform_event: + return + + self.transform_event = event + if self.transform_event_responder: + renpy.display.render.redraw(self, 0) + + def _hide(self, st, at, kind): + """ + Returns None if this displayable is ready to be hidden, or + a replacement displayable if it doesn't want to be hidden + quite yet. Kind is either "hide" or "replaced". + """ + + return None + + def _show(self): + """ + Called when the displayable is added to a scene list. + """ + + def _get_parameterized(self): + """ + If this is a ImageReference to a parameterized image, return + the get_parameterized() of the parameterized image. Otherwise, + return this displayable. + """ + + return self + + def _change_transform_child(self, child): + """ + If this is a transform, makes a copy of the transform and sets + the child of the innermost transform to this. Otherwise, + simply returns child. + """ + + return child + + + +class SceneListEntry(renpy.object.Object): + """ + Represents a scene list entry. Since this was replacing a tuple, + it should be treated as immutable after its initial creation. + """ + + def __init__(self, tag, zorder, show_time, animation_time, displayable, name): + self.tag = tag + self.zorder = zorder + self.show_time = show_time + self.animation_time = animation_time + self.displayable = displayable + self.name = name + + def __iter__(self): + return iter((self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)) + + def __getitem__(self, index): + return (self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)[index] + + def __repr__(self): + return "" % (self.tag, self.name, self.displayable) + + def copy(self): + return SceneListEntry( + self.tag, + self.zorder, + self.show_time, + self.animation_time, + self.displayable, + self.name) + + def update_time(self, time): + + rv = self + + if self.show_time is None or self.animation_time is None: + rv = self.copy() + rv.show_time = rv.show_time or time + rv.animation_time = rv.animation_time or time + + return rv + + +class SceneLists(renpy.object.Object): + """ + This stores the current scene lists that are being used to display + things to the user. + """ + + __version__ = 6 + + def after_setstate(self): + for i in renpy.config.layers + renpy.config.top_layers: + if i not in self.layers: + self.layers[i] = [ ] + self.at_list[i] = { } + self.layer_at_list[i] = (None, [ ]) + + def after_upgrade(self, version): + + if version < 1: + + self.at_list = { } + self.layer_at_list = { } + + for i in renpy.config.layers + renpy.config.top_layers: + self.at_list[i] = { } + self.layer_at_list[i] = (None, [ ]) + + if version < 3: + self.shown_window = False + + if version < 4: + for k in self.layers: + self.layers[k] = [ SceneListEntry(*(i + (None,)) ) for i in self.layers[k] ] + + self.additional_transient = [ ] + + if version < 5: + self.drag_group = None + + if version < 6: + self.shown = self.image_predict_info + + def __init__(self, oldsl, shown): + + super(SceneLists, self).__init__() + + # Has a window been shown as part of these scene lists? + self.shown_window = False + + # A map from layer name -> list(SceneListEntry) + self.layers = { } + + # A map from layer name -> tag -> at_list associated with that tag. + self.at_list = { } + + # A map from layer to (star time, at_list), where the at list has + # been applied to the layer as a whole. + self.layer_at_list = { } + + # The current shown images, + self.shown = shown + + # A list of (layer, tag) pairs that are considered to be + # transient. + self.additional_transient = [ ] + + # Either None, or a DragGroup that's used as the default for + # drags with names. + self.drag_group = None + + if oldsl: + + for i in renpy.config.layers + renpy.config.top_layers: + + try: + self.layers[i] = oldsl.layers[i][:] + except KeyError: + self.layers[i] = [ ] + + if i in oldsl.at_list: + self.at_list[i] = oldsl.at_list[i].copy() + self.layer_at_list[i] = oldsl.layer_at_list[i] + else: + self.at_list[i] = { } + self.layer_at_list[i] = (None, [ ]) + + for i in renpy.config.overlay_layers: + self.clear(i) + + self.replace_transient() + + self.focused = None + + self.drag_group = oldsl.drag_group + + else: + for i in renpy.config.layers + renpy.config.top_layers: + self.layers[i] = [ ] + self.at_list[i] = { } + self.layer_at_list[i] = (None, [ ]) + + self.music = None + self.focused = None + + def replace_transient(self): + """ + Replaces the contents of the transient display list with + a copy of the master display list. This is used after a + scene is displayed to get rid of transitions and interface + elements. + """ + + for i in renpy.config.transient_layers: + self.clear(i, True) + + for layer, tag in self.additional_transient: + self.remove(layer, tag) + + self.additional_transient = [ ] + + def transient_is_empty(self): + """ + This returns True if all transient layers are empty. This is + used by the rollback code, as we can't start a new rollback + if there is something in a transient layer (as things in the + transient layer may contain objects that cannot be pickled, + like lambdas.) + """ + + for i in renpy.config.transient_layers: + if self.layers[i]: + return False + + return True + + def transform_state(self, old_thing, new_thing): + """ + If the old thing is a transform, then move the state of that transform + to the new thing. + """ + + if old_thing is None: + return new_thing + + # Don't bother wrapping screens, as they can't be transformed. + if isinstance(new_thing, renpy.display.screen.ScreenDisplayable): + return new_thing + + old_transform = old_thing._get_parameterized() + if not isinstance(old_transform, renpy.display.motion.Transform): + return new_thing + + new_transform = new_thing._get_parameterized() + if not isinstance(new_transform, renpy.display.motion.Transform): + new_thing = new_transform = renpy.display.motion.Transform(child=new_thing) + + new_transform.take_state(old_transform) + return new_thing + + + def find_index(self, layer, tag, zorder, behind): + """ + This finds the spot in the named layer where we should insert the + displayable. It returns two things: an index at which the new thing + should be added, and an index at which the old thing should be hidden. + (Note that the indexes are relative to the current state of the list, + which may change on an add or remove.) + """ + + add_index = None + remove_index = None + + + for i, sle in enumerate(self.layers[layer]): + + if add_index is None: + + if sle.zorder == zorder: + if sle.tag and (sle.tag == tag or sle.tag in behind): + add_index = i + + elif sle.zorder > zorder: + add_index = i + + + if remove_index is None: + if (sle.tag and sle.tag == tag) or sle.displayable == tag: + remove_index = i + + + if add_index is None: + add_index = len(self.layers[layer]) + + return add_index, remove_index + + + def add(self, + layer, + thing, + key=None, + zorder=0, + behind=[ ], + at_list=[ ], + name=None, + atl=None, + default_transform=None, + transient=False): + """ + Adds something to this scene list. Some of these names are quite a bit + out of date. + + `thing` - The displayable to add. + + `key` - A string giving the tag associated with this thing. + + `zorder` - Where to place this thing in the zorder, an integer + A greater value means closer to the user. + + `behind` - A list of tags to place the thing behind. + + `at_list` - The at_list associated with this + displayable. Counterintunitively, this is not actually + applied, but merely stored for future use. + + `name` - The full name of the image being displayed. This is used for + image lookup. + + `atl` - If not None, an atl block applied to the thing. (This actually is + applied here.) + + `default_transform` - The default transform that is used to initialized + the values in the other transforms. + """ + + if not isinstance(thing, Displayable): + raise Exception("Attempting to show something that isn't a displayable:" + repr(thing)) + + if layer not in self.layers: + raise Exception("Trying to add something to non-existent layer '%s'." % layer) + + if key: + self.remove_hide_replaced(layer, key) + self.at_list[layer][key] = at_list + + if key and name: + self.shown.predict_show(layer, name) + + if transient: + self.additional_transient.append((layer, key)) + + l = self.layers[layer] + + if atl: + thing = renpy.display.motion.ATLTransform(atl, child=thing) + + add_index, remove_index = self.find_index(layer, key, zorder, behind) + + at = None + st = None + + if remove_index is not None: + sle = l[remove_index] + at = sle.animation_time + old = sle.displayable + + if (not atl and + not at_list and + renpy.config.keep_running_transform and + isinstance(old, renpy.display.motion.Transform)): + + thing = sle.displayable._change_transform_child(thing) + else: + thing = self.transform_state(l[remove_index].displayable, thing) + + thing.set_transform_event("replace") + thing._show() + + else: + + if not isinstance(thing, renpy.display.motion.Transform): + thing = self.transform_state(default_transform, thing) + + thing.set_transform_event("show") + thing._show() + + sle = SceneListEntry(key, zorder, st, at, thing, name) + l.insert(add_index, sle) + + if remove_index is not None: + if add_index <= remove_index: + remove_index += 1 + + self.hide_or_replace(layer, remove_index, "replaced") + + def hide_or_replace(self, layer, index, prefix): + """ + Hides or replaces the scene list entry at the given + index. `prefix` is a prefix that is used if the entry + decides it doesn't want to be hidden quite yet. + """ + + if index is None: + return + + l = self.layers[layer] + oldsle = l[index] + + now = get_time() + + st = oldsle.show_time or now + at = oldsle.animation_time or now + + if oldsle.tag: + + d = oldsle.displayable._hide(now - st, now - at, prefix) + + # _hide can mutate the layers, so we need to recompute + # index. + index = l.index(oldsle) + + if d is not None: + + sle = SceneListEntry( + prefix + "$" + oldsle.tag, + oldsle.zorder, + st, + at, + d, + None) + + l[index] = sle + + return + + l.pop(index) + + def get_all_displayables(self): + """ + Gets all displayables reachable from this scene list. + """ + + rv = [ ] + for l in self.layers.values(): + for sle in l: + rv.append(sle.displayable) + + return rv + + def remove_above(self, layer, thing): + """ + Removes everything on the layer that is closer to the user + than thing, which may be either a tag or a displayable. Thing must + be displayed, or everything will be removed. + """ + + for i in reversed(range(len(self.layers[layer]))): + + sle = self.layers[layer][i] + + if thing: + if sle.tag == thing or sle.displayable == thing: + break + + if sle.tag and "$" in sle.tag: + continue + + self.hide_or_replace(layer, i, "hide") + + def remove(self, layer, thing): + """ + Thing is either a key or a displayable. This iterates through the + named layer, searching for entries matching the thing. + When they are found, they are removed from the displaylist. + + It's not an error to remove something that isn't in the layer in + the first place. + """ + + if layer not in self.layers: + raise Exception("Trying to remove something from non-existent layer '%s'." % layer) + + _add_index, remove_index = self.find_index(layer, thing, 0, [ ]) + + if remove_index is not None: + tag = self.layers[layer][remove_index].tag + + if tag: + self.shown.predict_hide(layer, (tag,)) + self.at_list[layer].pop(tag, None) + + self.hide_or_replace(layer, remove_index, "hide") + + def clear(self, layer, hide=False): + """ + Clears the named layer, making it empty. + + If hide is True, then objects are hidden. Otherwise, they are + totally wiped out. + """ + + if not hide: + self.layers[layer] = [ ] + + else: + + # Have to iterate in reverse order, since otherwise + # the indexes might change. + for i in reversed(range(len(self.layers[layer]))): + self.hide_or_replace(layer, i, hide) + + self.at_list[layer].clear() + self.shown.predict_scene(layer) + self.layer_at_list[layer] = (None, [ ]) + + def set_layer_at_list(self, layer, at_list): + self.layer_at_list[layer] = (None, list(at_list)) + + def set_times(self, time): + """ + This finds entries with a time of None, and replaces that + time with the given time. + """ + + for l, (t, list) in list(self.layer_at_list.items()): #@ReservedAssignment + self.layer_at_list[l] = (t or time, list) + + for l, ll in self.layers.items(): + self.layers[l] = [ i.update_time(time) for i in ll ] + + def showing(self, layer, name): + """ + Returns true if something with the prefix of the given name + is found in the scene list. + """ + + return self.shown.showing(layer, name) + + def make_layer(self, layer, properties): + """ + Creates a Fixed with the given layer name and scene_list. + """ + + rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **properties) + rv.append_scene_list(self.layers[layer]) + + time, at_list = self.layer_at_list[layer] + + if at_list: + for a in at_list: + + if isinstance(a, renpy.display.motion.Transform): + rv = a(child=rv) + else: + rv = a(rv) + + f = renpy.display.layout.MultiBox(layout='fixed') + f.add(rv, time, time) + rv = f + + rv.layer_name = layer + return rv + + def remove_hide_replaced(self, layer, tag): + """ + Removes things that are hiding or replaced, that have the given + tag. + """ + + hide_tag = "hide$" + tag + replaced_tag = "replaced$" + tag + + l = self.layers[layer] + self.layers[layer] = [ i for i in l if i.tag != hide_tag and i.tag != replaced_tag ] + + def remove_hidden(self): + """ + Goes through all of the layers, and removes things that are + hidden and are no longer being kept alive by their hide + methods. + """ + + now = get_time() + + for l in self.layers: + newl = [ ] + + for sle in self.layers[l]: + + if sle.tag: + + if sle.tag.startswith("hide$"): + d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "hide") + if not d: + continue + + elif sle.tag.startswith("replaced$"): + d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "replaced") + if not d: + continue + + newl.append(sle) + + self.layers[l] = newl + + def get_displayable_by_tag(self, layer, tag): + """ + Returns the displayable on the layer with the given tag, or None + if no such displayable exists. Note that this will usually return + a Transform. + """ + + if layer not in self.layers: + raise Exception("Unknown layer %r." % layer) + + for sle in self.layers[layer]: + if sle.tag == tag: + return sle.displayable + + return None + + def get_displayable_by_name(self, layer, name): + """ + Returns the displayable on the layer with the given tag, or None + if no such displayable exists. Note that this will usually return + a Transform. + """ + + if layer not in self.layers: + raise Exception("Unknown layer %r." % layer) + + for sle in self.layers[layer]: + + if sle.name == name: + return sle.displayable + + return None + + +def scene_lists(index=-1): + """ + Returns either the current scenelists object, or the one for the + context at the given index. + """ + + return renpy.game.context(index).scene_lists + + +class Interface(object): + """ + This represents the user interface that interacts with the user. + It manages the Display objects that display things to the user, and + also handles accepting and responding to user input. + + @ivar display: The display that we used to display the screen. + + @ivar profile_time: The time of the last profiling. + + @ivar screenshot: A screenshot, or None if no screenshot has been + taken. + + @ivar old_scene: The last thing that was displayed to the screen. + + @ivar transition: A map from layer name to the transition that will + be applied the next time interact restarts. + + @ivar transition_time: A map from layer name to the time the transition + involving that layer started. + + @ivar transition_from: A map from layer name to the scene that we're + transitioning from on that layer. + + @ivar suppress_transition: If True, then the next transition will not + happen. + + @ivar force_redraw: If True, a redraw is forced. + + @ivar restart_interaction: If True, the current interaction will + be restarted. + + @ivar pushed_event: If not None, an event that was pushed back + onto the stack. + + @ivar mouse: The name of the mouse cursor to use during the current + interaction. + + @ivar ticks: The number of 20hz ticks. + + @ivar frame_time: The time at which we began drawing this frame. + + @ivar interact_time: The time of the start of the first frame of the current interact_core. + + @ivar time_event: A singleton ignored event. + + @ivar event_time: The time of the current event. + + @ivar timeout_time: The time at which the timeout will occur. + """ + + def __init__(self): + self.screenshot = None + self.old_scene = { } + self.transition = { } + self.ongoing_transition = { } + self.transition_time = { } + self.transition_from = { } + self.suppress_transition = False + self.quick_quit = False + self.force_redraw = False + self.restart_interaction = False + self.pushed_event = None + self.ticks = 0 + self.mouse = 'default' + self.timeout_time = None + self.last_event = None + self.current_context = None + self.roll_forward = None + + # Things to be preloaded. + self.preloads = [ ] + + # The time at which this draw occurs. + self.frame_time = 0 + + # The time when this interaction occured. + self.interact_time = None + + # The time we last tried to quit. + self.quit_time = 0 + + self.time_event = pygame.event.Event(TIMEEVENT) + self.redraw_event = pygame.event.Event(REDRAW) + + # Are we focused? + self.focused = True + + # Properties for each layer. + self.layer_properties = { } + + # Have we shown the window this interaction? + self.shown_window = False + + # Are we in fullscren mode? + self.fullscreen = False + + for layer in renpy.config.layers + renpy.config.top_layers: + if layer in renpy.config.layer_clipping: + x, y, w, h = renpy.config.layer_clipping[layer] + self.layer_properties[layer] = dict( + xpos = x, + xanchor = 0, + ypos = y, + yanchor = 0, + xmaximum = w, + ymaximum = h, + xminimum = w, + yminimum = h, + clipping = True, + ) + + else: + self.layer_properties[layer] = dict() + + + # A stack giving the values of self.transition and self.transition_time + # for contexts outside the current one. This is used to restore those + # in the case where nothing has changed in the new context. + self.transition_info_stack = [ ] + + # The time when the event was dispatched. + self.event_time = 0 + + # The time we saw the last mouse event. + self.mouse_event_time = None + + # Should we show the mouse? + self.show_mouse = True + + # Should we reset the display? + self.display_reset = False + + # The last size we were resized to. This lets us debounce the + # VIDEORESIZE event. + self.last_resize = None + + # Ensure that we kill off the presplash. + renpy.display.presplash.end() + + # Initialize pygame. + if pygame.version.vernum < (1, 8, 1): + raise Exception("Ren'Py requires pygame 1.8.1 to run.") + + try: + import pygame.macosx as macosx + macosx.init() #@UndefinedVariable + except: + pass + + try: + macosx.Video_AutoInit() #@UndefinedVariable + except: + pass + + pygame.font.init() + renpy.audio.audio.init() + renpy.display.joystick.init() + pygame.display.init() + + # Init timing. + init_time() + self.profile_time = get_time() + self.mouse_event_time = get_time() + + # The current window caption. + self.window_caption = None + + renpy.game.interface = self + renpy.display.interface = self + + # Are we in safe mode, from holding down shift at start? + self.safe_mode = False + if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT): + self.safe_mode = True + + # Setup the video mode. + self.set_mode() + + # Load the image fonts. + renpy.text.font.load_image_fonts() + + # Setup the android keymap. + if android is not None: + android.map_key(android.KEYCODE_BACK, pygame.K_PAGEUP) + android.map_key(android.KEYCODE_MENU, pygame.K_ESCAPE) + + # Double check, since at least on Linux, we can't set safe_mode until + # the window maps. + if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT): + self.safe_mode = True + + # Setup periodic event. + pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL) + + # Don't grab the screen. + pygame.event.set_grab(False) + + # Do we need a background screenshot? + self.bgscreenshot_needed = False + + # Event used to signal background screenshot taken. + self.bgscreenshot_event = threading.Event() + + # The background screenshot surface. + self.bgscreenshot_surface = None + + + def post_init(self): + # Setup. + + # Needed for Unity. + wmclass = renpy.config.save_directory or os.path.basename(sys.argv[0]) + os.environ['SDL_VIDEO_X11_WMCLASS'] = wmclass + + self.set_window_caption(force=True) + self.set_icon() + + if renpy.config.key_repeat is not None: + delay, repeat_delay = renpy.config.key_repeat + pygame.key.set_repeat(int(1000 * delay), int(1000 * repeat_delay)) + + def set_icon(self): + """ + This is called to set up the window icon. + """ + + # Window icon. + icon = renpy.config.window_icon + + if renpy.windows and renpy.config.windows_icon: + icon = renpy.config.windows_icon + + if icon: + + im = renpy.display.scale.image_load_unscaled( + renpy.loader.load(icon), + icon, + convert=False, + ) + + # Convert the aspect ratio to be square. + iw, ih = im.get_size() + imax = max(iw, ih) + square_im = renpy.display.pgrender.surface_unscaled((imax, imax), True) + square_im.blit(im, ( (imax-iw)/2, (imax-ih)/2 )) + im = square_im + + if renpy.windows and im.get_size() != (32, 32): + im = renpy.display.scale.real_smoothscale(im, (32, 32)) + + pygame.display.set_icon(im) + + + def set_window_caption(self, force=False): + caption = renpy.config.window_title + renpy.store._window_subtitle + if not force and caption == self.window_caption: + return + + self.window_caption = caption + pygame.display.set_caption(caption.encode("utf-8")) + + def iconify(self): + pygame.display.iconify() + + def get_draw_constructors(self): + """ + Figures out the list of draw constructors to try. + """ + + renderer = renpy.game.preferences.renderer + renderer = os.environ.get("RENPY_RENDERER", renderer) + + if self.safe_mode: + renderer = "sw" + + renpy.config.renderer = renderer + + if renderer == "auto": + if renpy.windows: + renderers = [ "gl", "angle", "sw" ] + else: + renderers = [ "gl", "sw" ] + else: + renderers = [ renderer, "sw" ] + + draw_objects = { } + + def make_draw(name, mod, cls, *args): + if name not in renderers: + return False + + try: + __import__(mod) + module = sys.modules[mod] + draw_class = getattr(module, cls) + draw_objects[name] = draw_class(*args) + return True + + except: + renpy.display.log.write("Couldn't import {0} renderer:".format(name)) + renpy.display.log.exception() + + return False + + if renpy.windows: + has_angle = make_draw("angle", "renpy.angle.gldraw", "GLDraw") + else: + has_angle = False + + make_draw("gl", "renpy.gl.gldraw", "GLDraw", not has_angle) + make_draw("sw", "renpy.display.swdraw", "SWDraw") + + rv = [ ] + + def append_draw(name): + if name in draw_objects: + rv.append(draw_objects[name]) + else: + renpy.display.log.write("Unknown renderer: {0}".format(name)) + + for i in renderers: + append_draw(i) + + return rv + + + def kill_textures(self): + renpy.display.render.free_memory() + renpy.text.text.layout_cache_clear() + + def kill_textures_and_surfaces(self): + """ + Kill all textures and surfaces that are loaded. + """ + + self.kill_textures() + + renpy.display.im.cache.clear() + renpy.display.module.bo_cache = None + + def set_mode(self, physical_size=None): + """ + This sets the video mode. It also picks the draw object. + """ + + # Ensure that we kill off the movie when changing screen res. + if renpy.display.draw and renpy.display.draw.info["renderer"] == "sw": + renpy.display.video.movie_stop(clear=False) + + if self.display_reset: + renpy.display.draw.deinit() + + if renpy.display.draw.info["renderer"] == "angle": + renpy.display.draw.quit() + + renpy.display.render.free_memory() + renpy.display.im.cache.clear() + renpy.text.text.layout_cache_clear() + + renpy.display.module.bo_cache = None + + self.kill_textures_and_surfaces() + + self.display_reset = False + + virtual_size = (renpy.config.screen_width, renpy.config.screen_height) + + if physical_size is None: + if renpy.android or renpy.game.preferences.physical_size is None: #@UndefinedVariable + physical_size = (renpy.config.screen_width, renpy.config.screen_height) + else: + physical_size = renpy.game.preferences.physical_size + + # Setup screen. + fullscreen = renpy.game.preferences.fullscreen + + # If we're in fullscreen mode, and changing to another mode, go to + # windowed mode first. + s = pygame.display.get_surface() + if s and (s.get_flags() & pygame.FULLSCREEN): + fullscreen = False + + old_fullscreen = self.fullscreen + self.fullscreen = fullscreen + + if os.environ.get('RENPY_DISABLE_FULLSCREEN', False): + fullscreen = False + self.fullscreen = renpy.game.preferences.fullscreen + + if renpy.display.draw: + draws = [ renpy.display.draw ] + else: + draws = self.get_draw_constructors() + + for draw in draws: + if draw.set_mode(virtual_size, physical_size, fullscreen): + renpy.display.draw = draw + break + else: + pygame.display.quit() + else: + # Ensure we don't get stuck in fullscreen. + renpy.game.preferences.fullscreen = False + raise Exception("Could not set video mode.") + + # Save the video size. + if renpy.config.save_physical_size and not fullscreen and not old_fullscreen: + renpy.game.preferences.physical_size = renpy.display.draw.get_physical_size() + + if android: + android.init() + + # We need to redraw the (now blank) screen. + self.force_redraw = True + + # Assume we have focus until told otherwise. + self.focused = True + + # Assume we're not minimized. + self.minimized = False + + # Force an interaction restart. + self.restart_interaction = True + + + def draw_screen(self, root_widget, fullscreen_video, draw): + + surftree = renpy.display.render.render_screen( + root_widget, + renpy.config.screen_width, + renpy.config.screen_height, + ) + + if draw: + renpy.display.draw.draw_screen(surftree, fullscreen_video) + + renpy.display.render.mark_sweep() + renpy.display.focus.take_focuses() + + self.surftree = surftree + self.fullscreen_video = fullscreen_video + + + def take_screenshot(self, scale, background=False): + """ + This takes a screenshot of the current screen, and stores it so + that it can gotten using get_screenshot() + + `background` + If true, we're in a background thread. So queue the request + until it can be handled by the main thread. + """ + + if background: + self.bgscreenshot_event.clear() + self.bgscreenshot_needed = True + self.bgscreenshot_event.wait() + + window = self.bgscreenshot_surface + self.bgscreenshot_surface = None + + else: + + window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video) + + surf = renpy.display.pgrender.copy_surface(window, True) + surf = renpy.display.scale.smoothscale(surf, scale) + surf = surf.convert() + + sio = io.StringIO() + renpy.display.module.save_png(surf, sio, 0) + self.screenshot = sio.getvalue() + sio.close() + + + def save_screenshot(self, filename): + """ + Saves a full-size screenshot in the given filename. + """ + + window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video) + + if renpy.config.screenshot_crop: + window = window.subsurface(renpy.config.screenshot_crop) + + try: + renpy.display.scale.image_save_unscaled(window, filename) + except: + if renpy.config.debug: + raise + pass + + + def get_screenshot(self): + """ + Gets the current screenshot, as a string. Returns None if there isn't + a current screenshot. + """ + + rv = self.screenshot + + if not rv: + self.take_screenshot((renpy.config.thumbnail_width, renpy.config.thumbnail_height)) + rv = self.screenshot + self.lose_screenshot() + + return rv + + + def lose_screenshot(self): + """ + This deallocates the saved screenshot. + """ + + self.screenshot = None + + + def show_window(self): + + if not renpy.store._window: + return + + if renpy.game.context().scene_lists.shown_window: + return + + if renpy.config.empty_window: + renpy.config.empty_window() + + def do_with(self, trans, paired, clear=False): + + if renpy.config.with_callback: + trans = renpy.config.with_callback(trans, paired) + + if (not trans) or self.suppress_transition: + self.with_none() + return False + else: + self.set_transition(trans) + return self.interact(trans_pause=True, + suppress_overlay=not renpy.config.overlay_during_with, + mouse='with', + clear=clear) + + def with_none(self): + """ + Implements the with None command, which sets the scene we will + be transitioning from. + """ + + renpy.exports.say_attributes = None + + # Show the window, if that's necessary. + self.show_window() + + # Compute the overlay. + self.compute_overlay() + + scene_lists = renpy.game.context().scene_lists + + # Compute the scene. + self.old_scene = self.compute_scene(scene_lists) + + # Get rid of transient things. + + for i in renpy.config.overlay_layers: + scene_lists.clear(i) + + scene_lists.replace_transient() + scene_lists.shown_window = False + + + def set_transition(self, transition, layer=None, force=False): + """ + Sets the transition that will be performed as part of the next + interaction. + """ + + if self.suppress_transition and not force: + return + + if transition is None: + self.transition.pop(layer, None) + else: + self.transition[layer] = transition + + + def event_peek(self): + """ + This peeks the next event. It returns None if no event exists. + """ + + if self.pushed_event: + return self.pushed_event + + ev = pygame.event.poll() + + if ev.type == pygame.NOEVENT: + # Seems to prevent the CPU from speeding up. + renpy.display.draw.event_peek_sleep() + return None + + self.pushed_event = ev + + return ev + + def event_poll(self): + """ + Called to busy-wait for an event while we're waiting to + redraw a frame. + """ + + if self.pushed_event: + rv = self.pushed_event + self.pushed_event = None + else: + rv = pygame.event.poll() + + self.last_event = rv + + return rv + + + def event_wait(self): + """ + This is in its own function so that we can track in the + profiler how much time is spent in interact. + """ + + if self.pushed_event: + rv = self.pushed_event + self.pushed_event = None + self.last_event = rv + return rv + + # Handle a request for a background screenshot. + if self.bgscreenshot_needed: + self.bgscreenshot_needed = False + self.bgscreenshot_surface = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video) + self.bgscreenshot_event.set() + + try: + cpu_idle.set() + ev = pygame.event.wait() + finally: + cpu_idle.clear() + + self.last_event = ev + + return ev + + + def compute_overlay(self): + + if renpy.store.suppress_overlay: + return + + # Figure out what the overlay layer should look like. + renpy.ui.layer("overlay") + + for i in renpy.config.overlay_functions: + i() + + if renpy.game.context().scene_lists.shown_window: + for i in renpy.config.window_overlay_functions: + i() + + renpy.ui.close() + + + def compute_scene(self, scene_lists): + """ + This converts scene lists into a dictionary mapping layer + name to a Fixed containing that layer. + """ + + rv = { } + + for layer in renpy.config.layers + renpy.config.top_layers: + rv[layer] = scene_lists.make_layer(layer, self.layer_properties[layer]) + + root = renpy.display.layout.MultiBox(layout='fixed') + root.layers = { } + + for layer in renpy.config.layers: + root.layers[layer] = rv[layer] + root.add(rv[layer]) + rv[None] = root + + return rv + + + def quit_event(self): + """ + This is called to handle the user invoking a quit. + """ + + if self.quit_time > (time.time() - .75): + raise renpy.game.QuitException() + + if renpy.config.quit_action is not None: + self.quit_time = time.time() + + # Make the screen more suitable for interactions. + renpy.exports.movie_stop(only_fullscreen=True) + renpy.store.mouse_visible = True + + renpy.display.behavior.run(renpy.config.quit_action) + else: + raise renpy.game.QuitException() + + + def get_mouse_info(self): + # Figure out if the mouse visibility algorithm is hiding the mouse. + if self.mouse_event_time + renpy.config.mouse_hide_time < renpy.display.core.get_time(): + visible = False + else: + visible = renpy.store.mouse_visible and (not renpy.game.less_mouse) + + visible = visible and self.show_mouse + + # If not visible, hide the mouse. + if not visible: + return False, 0, 0, None + + # Deal with a hardware mouse, the easy way. + if not renpy.config.mouse: + return True, 0, 0, None + + # Deal with the mouse going offscreen. + if not self.focused: + return False, 0, 0, None + + mouse_kind = renpy.display.focus.get_mouse() or self.mouse + + # Figure out the mouse animation. + if mouse_kind in renpy.config.mouse: + anim = renpy.config.mouse[mouse_kind] + else: + anim = renpy.config.mouse[getattr(renpy.store, 'default_mouse', 'default')] + + img, x, y = anim[self.ticks % len(anim)] + tex = renpy.display.im.load_image(img) + + return False, x, y, tex + + def drawn_since(self, seconds_ago): + """ + Returns true if the screen has been drawn in the last `seconds_ago`, + and false otherwise. + """ + + return (get_time() - self.frame_time) <= seconds_ago + + def android_check_suspend(self): + + if android.check_pause(): + + android.sound.pause_all() + + pygame.time.set_timer(PERIODIC, 0) + pygame.time.set_timer(REDRAW, 0) + pygame.time.set_timer(TIMEEVENT, 0) + + # The game has to be saved. + renpy.loadsave.save("_reload-1") + + android.wait_for_resume() + + # Since we came back to life, we can get rid of the + # auto-reload. + renpy.loadsave.unlink_save("_reload-1") + + pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL) + + android.sound.unpause_all() + + def iconified(self): + """ + Called when we become an icon. + """ + + if self.minimized: + return + + self.minimized = True + + renpy.display.log.write("The window was minimized.") + + + def restored(self): + """ + Called when we are restored from being an icon. + """ + + # This is necessary on Windows/DirectX/Angle, as otherwise we get + # a blank screen. + + if not self.minimized: + return + + self.minimized = False + + renpy.display.log.write("The window was restored.") + + if renpy.windows: + self.display_reset = True + self.set_mode(self.last_resize) + + def enter_context(self): + """ + Called when we enter a new context. + """ + + # Stop ongoing transitions. + self.ongoing_transition.clear() + self.transition_from.clear() + self.transition_time.clear() + + def post_time_event(self): + """ + Posts a time_event object to the queue. + """ + + try: + pygame.event.post(self.time_event) + except: + pass + + def interact(self, clear=True, suppress_window=False, **kwargs): + """ + This handles an interaction, restarting it if necessary. All of the + keyword arguments are passed off to interact_core. + """ + + # Cancel magic error reporting. + renpy.bootstrap.report_error = None + + context = renpy.game.context() + + if context.interacting: + raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.") + + context.interacting = True + + + # Show a missing window. + if not suppress_window: + self.show_window() + + # These things can be done once per interaction. + + preloads = self.preloads + self.preloads = [ ] + + try: + renpy.game.after_rollback = False + + for i in renpy.config.start_interact_callbacks: + i() + + repeat = True + + while repeat: + repeat, rv = self.interact_core(preloads=preloads, **kwargs) + + return rv + + finally: + + context.interacting = False + + # Clean out transient stuff at the end of an interaction. + if clear: + scene_lists = renpy.game.context().scene_lists + scene_lists.replace_transient() + + self.ongoing_transition = { } + self.transition_time = { } + self.transition_from = { } + + self.restart_interaction = True + + renpy.game.context().scene_lists.shown_window = False + + def interact_core(self, + show_mouse=True, + trans_pause=False, + suppress_overlay=False, + suppress_underlay=False, + mouse='default', + preloads=[], + roll_forward=None, + ): + + """ + This handles one cycle of displaying an image to the user, + and then responding to user input. + + @param show_mouse: Should the mouse be shown during this + interaction? Only advisory, and usually doesn't work. + + @param trans_pause: If given, we must have a transition. Should we + add a pause behavior during the transition? + + @param suppress_overlay: This suppresses the display of the overlay. + @param suppress_underlay: This suppresses the display of the underlay. + """ + + self.roll_forward = roll_forward + self.show_mouse = show_mouse + + suppress_transition = renpy.config.skipping or renpy.game.less_updates + + # The global one. + self.suppress_transition = False + + # Figure out transitions. + for k in self.transition: + if k not in self.old_scene: + continue + + self.ongoing_transition[k] = self.transition[k] + self.transition_from[k] = self.old_scene[k] + self.transition_time[k] = None + + self.transition.clear() + + if suppress_transition: + self.ongoing_transition.clear() + self.transition_from.clear() + self.transition_time.clear() + + ## Safety condition, prevents deadlocks. + if trans_pause: + if not self.ongoing_transition: + return False, None + if None not in self.ongoing_transition: + return False, None + if suppress_transition: + return False, None + if not self.old_scene: + return False, None + + # Check to see if the language has changed. + renpy.translation.check_language() + + # We just restarted. + self.restart_interaction = False + + # Setup the mouse. + self.mouse = mouse + + # The start and end times of this interaction. + start_time = get_time() + end_time = start_time + + # frames = 0 + + for i in renpy.config.interact_callbacks: + i() + + # Set the window caption. + self.set_window_caption() + + # Tick time forward. + renpy.display.im.cache.tick() + renpy.text.text.layout_cache_tick() + renpy.display.predict.reset() + + # Cleare the size groups. + renpy.display.layout.size_groups.clear() + + # Clear some events. + pygame.event.clear((pygame.MOUSEMOTION, + PERIODIC, + TIMEEVENT, + REDRAW)) + + # Add a single TIMEEVENT to the queue. + self.post_time_event() + + # Figure out the scene list we want to show. + scene_lists = renpy.game.context().scene_lists + + # Remove the now-hidden things. + scene_lists.remove_hidden() + + # Compute the overlay. + if not suppress_overlay: + self.compute_overlay() + + # The root widget of everything that is displayed on the screen. + root_widget = renpy.display.layout.MultiBox(layout='fixed') + root_widget.layers = { } + + # A list of widgets that are roots of trees of widgets that are + # considered for focusing. + focus_roots = [ ] + + # Add the underlay to the root widget. + if not suppress_underlay: + for i in renpy.config.underlay: + root_widget.add(i) + focus_roots.append(i) + + if roll_forward is not None: + rfw = renpy.display.behavior.RollForward(roll_forward) + root_widget.add(rfw) + focus_roots.append(rfw) + + # Figure out the scene. (All of the layers, and the root.) + scene = self.compute_scene(scene_lists) + + # If necessary, load all images here. + for w in scene.values(): + try: + renpy.display.predict.displayable(w) + except: + pass + + # The root widget of all of the layers. + layers_root = renpy.display.layout.MultiBox(layout='fixed') + layers_root.layers = { } + + def add_layer(where, layer): + + scene_layer = scene[layer] + focus_roots.append(scene_layer) + + if (self.ongoing_transition.get(layer, None) and + not suppress_transition): + + trans = self.ongoing_transition[layer]( + old_widget=self.transition_from[layer], + new_widget=scene_layer) + + if not isinstance(trans, Displayable): + raise Exception("Expected transition to be a displayable, not a %r" % trans) + + transition_time = self.transition_time.get(layer, None) + + where.add(trans, transition_time, transition_time) + where.layers[layer] = trans + + else: + where.layers[layer] = scene_layer + where.add(scene_layer) + + # Add layers (perhaps with transitions) to the layers root. + for layer in renpy.config.layers: + add_layer(layers_root, layer) + + # Add layers_root to root_widget, perhaps through a transition. + if (self.ongoing_transition.get(None, None) and + not suppress_transition): + + old_root = renpy.display.layout.MultiBox(layout='fixed') + old_root.layers = { } + + for layer in renpy.config.layers: + d = self.transition_from[None].layers[layer] + old_root.layers[layer] = d + old_root.add(d) + + trans = self.ongoing_transition[None]( + old_widget=old_root, + new_widget=layers_root) + + if not isinstance(trans, Displayable): + raise Exception("Expected transition to be a displayable, not a %r" % trans) + + trans._show() + + transition_time = self.transition_time.get(None, None) + root_widget.add(trans, transition_time, transition_time) + + if trans_pause: + sb = renpy.display.behavior.SayBehavior() + root_widget.add(sb) + focus_roots.append(sb) + + pb = renpy.display.behavior.PauseBehavior(trans.delay) + root_widget.add(pb, transition_time, transition_time) + focus_roots.append(pb) + + else: + root_widget.add(layers_root) + + # Add top_layers to the root_widget. + for layer in renpy.config.top_layers: + add_layer(root_widget, layer) + + prediction_coroutine = renpy.display.predict.prediction_coroutine(root_widget) + prediction_coroutine.send(None) + + # Clean out the registered adjustments. + renpy.display.behavior.adj_registered.clear() + + # Clean up some movie-related things. + renpy.display.video.early_interact() + + # Call per-interaction code for all widgets. + root_widget.visit_all(lambda i : i.per_interact()) + + # Now, update various things regarding scenes and transitions, + # so we are ready for a new interaction or a restart. + self.old_scene = scene + + # Okay, from here on we now have a single root widget (root_widget), + # which we will try to show to the user. + + # Figure out what should be focused. + renpy.display.focus.before_interact(focus_roots) + + # Redraw the screen. + renpy.display.render.process_redraws() + needs_redraw = True + + # First pass through the while loop? + first_pass = True + + # We don't yet know when the interaction began. + self.interact_time = None + + # We only want to do autosave once. + did_autosave = False + + old_timeout_time = None + old_redraw_time = None + + rv = None + + # Start sound. + renpy.audio.audio.interact() + + # How long until we redraw. + redraw_in = 3600 + + # Have we drawn a frame yet? + video_frame_drawn = False + + # This try block is used to force cleanup even on termination + # caused by an exception propagating through this function. + try: + + while rv is None: + + # Check for a change in fullscreen preference. + if self.fullscreen != renpy.game.preferences.fullscreen or self.display_reset: + self.set_mode() + needs_redraw = True + + # Check for suspend. + if android: + self.android_check_suspend() + + # Redraw the screen. + if (self.force_redraw or + ((first_pass or not pygame.event.peek(ALL_EVENTS)) and + renpy.display.draw.should_redraw(needs_redraw, first_pass))): + + self.force_redraw = False + + # If we have a movie, start showing it. + fullscreen_video = renpy.display.video.interact() + + # Clean out the redraws, if we have to. + # renpy.display.render.kill_redraws() + + # Draw the screen. + self.frame_time = get_time() + + if not self.interact_time: + self.interact_time = self.frame_time + + self.draw_screen(root_widget, fullscreen_video, (not fullscreen_video) or video_frame_drawn) + + if first_pass: + scene_lists.set_times(self.interact_time) + for k, v in self.transition_time.items(): + if v is None: + self.transition_time[k] = self.interact_time + + renpy.config.frames += 1 + + # If profiling is enabled, report the profile time. + if renpy.config.profile : + new_time = get_time() + + if new_time - self.profile_time > .015: + print("Profile: Redraw took %f seconds." % (new_time - self.frame_time)) + print("Profile: %f seconds to complete event." % (new_time - self.profile_time)) + + if first_pass and self.last_event: + x, y = renpy.display.draw.get_mouse_pos() + renpy.display.focus.mouse_handler(self.last_event, x, y, default=False) + + needs_redraw = False + first_pass = False + + pygame.time.set_timer(REDRAW, 0) + pygame.event.clear([REDRAW]) + old_redraw_time = None + + # Draw the mouse, if it needs drawing. + renpy.display.draw.update_mouse() + + # See if we want to restart the interaction entirely. + if self.restart_interaction: + return True, None + + # Determine if we need a redraw. (We want to run these + # functions, so we put them first to prevent short-circuiting.) + + if renpy.display.video.frequent(): + needs_redraw = True + video_frame_drawn = True + + needs_redraw = renpy.display.video.frequent() or needs_redraw + needs_redraw = renpy.display.render.process_redraws() or needs_redraw + + # How many seconds until we timeout. + timeout_in = 3600 + + # Handle the redraw timer. + redraw_time = renpy.display.render.redraw_time() + + if (redraw_time is not None) and not needs_redraw: + if redraw_time != old_redraw_time: + time_left = redraw_time - get_time() + time_left = min(time_left, 3600) + redraw_in = time_left + + if time_left <= 0: + try: + pygame.event.post(self.redraw_event) + except: + pass + pygame.time.set_timer(REDRAW, 0) + else: + pygame.time.set_timer(REDRAW, max(int(time_left * 1000), 1)) + + old_redraw_time = redraw_time + else: + redraw_in = 3600 + pygame.time.set_timer(REDRAW, 0) + + # Handle the timeout timer. + if not self.timeout_time: + pygame.time.set_timer(TIMEEVENT, 0) + else: + time_left = self.timeout_time - get_time() + time_left = min(time_left, 3600) + timeout_in = time_left + + if time_left <= 0: + self.timeout_time = None + pygame.time.set_timer(TIMEEVENT, 0) + self.post_time_event() + elif self.timeout_time != old_timeout_time: + # Always set to at least 1ms. + pygame.time.set_timer(TIMEEVENT, int(time_left * 1000 + 1)) + old_timeout_time = self.timeout_time + + # Predict images, if we haven't done so already. + while prediction_coroutine is not None: + + # Can we do expensive prediction? + expensive_predict = not (needs_redraw or self.event_peek() or renpy.audio.music.is_playing("movie")) + + result = prediction_coroutine.send(expensive_predict) + + if not result: + prediction_coroutine = None + break + + if not expensive_predict: + break + + # If we need to redraw again, do it if we don't have an + # event going on. + if needs_redraw and not self.event_peek(): + if renpy.config.profile: + self.profile_time = get_time() + continue + + # Handle autosaving, as necessary. + if not did_autosave and not needs_redraw and not self.event_peek() and redraw_in > .25 and timeout_in > .25: + renpy.loadsave.autosave() + did_autosave = True + + if needs_redraw or renpy.display.video.playing(): + ev = self.event_poll() + else: + ev = self.event_wait() + + if ev.type == pygame.NOEVENT: + continue + + if renpy.config.profile: + self.profile_time = get_time() + + # Try to merge an TIMEEVENT with other timeevents. + if ev.type == TIMEEVENT: + old_timeout_time = None + pygame.event.clear([TIMEEVENT]) + + # On Android, where we have multiple mouse buttons, we can + # merge a mouse down and mouse up event with its successor. This + # prevents us from getting overwhelmed with too many events on + # a multitouch screen. + if android and (ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP): + pygame.event.clear(ev.type) + + # Handle redraw timeouts. + if ev.type == REDRAW: + pygame.event.clear([REDRAW]) + old_redraw_time = None + continue + + # Handle periodic events. This includes updating the mouse timers (and through the loop, + # the mouse itself), and the audio system periodic calls. + if ev.type == PERIODIC: + events = 1 + len(pygame.event.get([PERIODIC])) + self.ticks += events + + if renpy.config.periodic_callback: + renpy.config.periodic_callback() + + renpy.audio.audio.periodic() + continue + + # This can set the event to None, to ignore it. + ev = renpy.display.joystick.event(ev) + if not ev: + continue + + # Handle skipping. + renpy.display.behavior.skipping(ev) + + # Handle quit specially for now. + if ev.type == pygame.QUIT: + self.quit_event() + continue + + # Handle videoresize. + if ev.type == pygame.VIDEORESIZE: + evs = pygame.event.get([pygame.VIDEORESIZE]) + if len(evs): + ev = evs[-1] + + if self.last_resize != ev.size: + self.last_resize = ev.size + self.set_mode((ev.w, ev.h)) + + continue + + if ev.type == pygame.MOUSEMOTION or \ + ev.type == pygame.MOUSEBUTTONDOWN or \ + ev.type == pygame.MOUSEBUTTONUP: + + self.mouse_event_time = renpy.display.core.get_time() + + # Merge mousemotion events. + if ev.type == pygame.MOUSEMOTION: + evs = pygame.event.get([pygame.MOUSEMOTION]) + if len(evs): + ev = evs[-1] + + if renpy.windows: + self.focused = True + + # Handle focus notifications. + if ev.type == pygame.ACTIVEEVENT: + if ev.state & 1: + self.focused = ev.gain + + if ev.state & 4: + if ev.gain: + self.restored() + else: + self.iconified() + + pygame.key.set_mods(0) + + # This returns the event location. It also updates the + # mouse state as necessary. + x, y = renpy.display.draw.mouse_event(ev) + + if not self.focused: + x = -1 + y = -1 + + self.event_time = end_time = get_time() + + try: + + # Handle the event normally. + rv = renpy.display.focus.mouse_handler(ev, x, y) + + + if rv is None: + rv = root_widget.event(ev, x, y, 0) + + if rv is None: + rv = renpy.display.focus.key_handler(ev) + + if rv is not None: + break + + # Handle displayable inspector. + if renpy.config.inspector and renpy.display.behavior.inspector(ev): + l = self.surftree.main_displayables_at_point(x, y, renpy.config.transient_layers + renpy.config.context_clear_layers + renpy.config.overlay_layers) + renpy.game.invoke_in_new_context(renpy.config.inspector, l) + + except IgnoreEvent: + # An ignored event can change the timeout. So we want to + # process an TIMEEVENT to ensure that the timeout is + # set correctly. + self.post_time_event() + + + # Check again after handling the event. + needs_redraw |= renpy.display.render.process_redraws() + + if self.restart_interaction: + return True, None + + # If we were trans-paused and rv is true, suppress + # transitions up to the next interaction. + if trans_pause and rv: + self.suppress_transition = True + + # But wait, there's more! The finally block runs some cleanup + # after this. + return False, rv + + except EndInteraction as e: + return False, e.value + + finally: + + renpy.exports.say_attributes = None + + # Clean out the overlay layers. + for i in renpy.config.overlay_layers: + scene_lists.clear(i) + + # Stop ongoing preloading. + renpy.display.im.cache.end_tick() + + # We no longer disable periodic between interactions. + # pygame.time.set_timer(PERIODIC, 0) + + pygame.time.set_timer(TIMEEVENT, 0) + pygame.time.set_timer(REDRAW, 0) + + renpy.game.context().runtime += end_time - start_time + + # Restart the old interaction, which also causes a + # redraw if needed. + self.restart_interaction = True + + # print "It took", frames, "frames." + + def timeout(self, offset): + if offset < 0: + return + + if self.timeout_time: + self.timeout_time = min(self.event_time + offset, self.timeout_time) + else: + self.timeout_time = self.event_time + offset + -- cgit v1.2.3-54-g00ecf