1# Copyright 2004-2021 Tom Rothamel <pytom@bishoujo.us>
2#
3# Permission is hereby granted, free of charge, to any person
4# obtaining a copy of this software and associated documentation files
5# (the "Software"), to deal in the Software without restriction,
6# including without limitation the rights to use, copy, modify, merge,
7# publish, distribute, sublicense, and/or sell copies of the Software,
8# and to permit persons to whom the Software is furnished to do so,
9# subject to the following conditions:
10#
11# The above copyright notice and this permission notice shall be
12# included in all copies or substantial portions of the Software.
13#
14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22# This file contains code for initializing and managing the display
23# window.
24
25from __future__ import division, absolute_import, with_statement, print_function, unicode_literals
26from renpy.compat import *
27
28import renpy.display
29import renpy.audio
30import renpy.text
31import renpy.test
32
33import pygame_sdl2 as pygame
34
35import sys
36import os
37import time
38import io
39import threading
40import copy
41import gc
42import atexit
43
44import_time = time.time()
45
46try:
47    import android # @UnresolvedImport
48    android.init # Check to see if we have the right module.
49except:
50    android = None
51
52if renpy.emscripten:
53    import emscripten
54
55TIMEEVENT = pygame.event.register("TIMEEVENT")
56PERIODIC = pygame.event.register("PERIODIC")
57REDRAW = pygame.event.register("REDRAW")
58EVENTNAME = pygame.event.register("EVENTNAME")
59
60# All events except for TIMEEVENT and REDRAW
61ALL_EVENTS = set(pygame.event.get_standard_events()) # @UndefinedVariable
62ALL_EVENTS.add(PERIODIC)
63ALL_EVENTS.add(EVENTNAME)
64
65enabled_events = {
66    pygame.QUIT,
67
68    pygame.APP_TERMINATING,
69    pygame.APP_LOWMEMORY,
70    pygame.APP_WILLENTERBACKGROUND,
71    pygame.APP_DIDENTERBACKGROUND,
72    pygame.APP_WILLENTERFOREGROUND,
73    pygame.APP_DIDENTERFOREGROUND,
74
75    pygame.WINDOWEVENT,
76    pygame.SYSWMEVENT,
77
78    pygame.KEYDOWN,
79    pygame.KEYUP,
80
81    pygame.TEXTEDITING,
82    pygame.TEXTINPUT,
83    pygame.KEYMAPCHANGED,
84
85    pygame.MOUSEMOTION,
86    pygame.MOUSEBUTTONDOWN,
87    pygame.MOUSEBUTTONUP,
88    pygame.MOUSEWHEEL,
89
90    pygame.JOYAXISMOTION,
91    pygame.JOYHATMOTION,
92    pygame.JOYBALLMOTION,
93    pygame.JOYBUTTONDOWN,
94    pygame.JOYBUTTONUP,
95    pygame.JOYDEVICEADDED,
96    pygame.JOYDEVICEREMOVED,
97
98    pygame.CONTROLLERAXISMOTION,
99    pygame.CONTROLLERBUTTONDOWN,
100    pygame.CONTROLLERBUTTONUP,
101    pygame.CONTROLLERDEVICEADDED,
102    pygame.CONTROLLERDEVICEREMOVED,
103
104    pygame.RENDER_TARGETS_RESET,
105
106    TIMEEVENT,
107    PERIODIC,
108    REDRAW,
109    EVENTNAME,
110    }
111
112# The number of msec between periodic events.
113PERIODIC_INTERVAL = 50
114
115# Time management.
116time_base = 0.0
117time_mult = 1.0
118
119
120def init_time():
121    warp = os.environ.get("RENPY_TIMEWARP", "1.0")
122
123    global time_base
124    global time_mult
125
126    time_base = time.time()
127    time_mult = float(warp)
128
129
130def get_time():
131    t = time.time()
132    return time_base + (t - time_base) * time_mult
133
134
135def get_size():
136    """
137    Returns the screen size. Always returns at least 256, 256, to make sure
138    that we don't divide by zero.
139    """
140
141    size = pygame.display.get_size()
142
143    if not size:
144        return size
145
146    if size[0] >= 256 and size[1] >= 256:
147        return size
148
149    return (max(size[0], 256), max(size[1], 256))
150
151
152def displayable_by_tag(layer, tag):
153    """
154    Get the displayable on the given layer with the given tag.
155    """
156
157    return renpy.game.context().scene_lists.get_displayable_by_tag(layer, tag)
158
159
160class IgnoreEvent(Exception):
161    """
162    Exception that is raised when we want to ignore an event, but
163    also don't want to return anything.
164    """
165
166    pass
167
168
169class EndInteraction(Exception):
170    """
171    Exception that can be raised (for example, during the render method of
172    a displayable) to end the current interaction immediately.
173    """
174
175    def __init__(self, value):
176        self.value = value
177
178
179class absolute(float):
180    """
181    This represents an absolute float coordinate.
182    """
183    __slots__ = [ ]
184
185
186def place(width, height, sw, sh, placement):
187    """
188    Performs the Ren'Py placement algorithm.
189
190    `width`, `height`
191        The width and height of the area the image will be
192        placed in.
193
194    `size`
195        The size of the image to be placed.
196
197    `placement`
198        The tuple returned by Displayable.get_placement().
199    """
200
201    xpos, ypos, xanchor, yanchor, xoffset, yoffset, _subpixel = placement
202
203    if xpos is None:
204        xpos = 0
205    if ypos is None:
206        ypos = 0
207    if xanchor is None:
208        xanchor = 0
209    if yanchor is None:
210        yanchor = 0
211    if xoffset is None:
212        xoffset = 0
213    if yoffset is None:
214        yoffset = 0
215
216    # We need to use type, since isinstance(absolute(0), float).
217    if xpos.__class__ is float:
218        xpos *= width
219
220    if xanchor.__class__ is float:
221        xanchor *= sw
222
223    x = xpos + xoffset - xanchor
224
225    if ypos.__class__ is float:
226        ypos *= height
227
228    if yanchor.__class__ is float:
229        yanchor *= sh
230
231    y = ypos + yoffset - yanchor
232
233    return x, y
234
235
236class DisplayableArguments(renpy.object.Object):
237    """
238    Represents a set of arguments that can be passed to a duplicated
239    displayable.
240    """
241
242    # The name of the displayable without any arguments.
243    name = ()
244
245    # Arguments supplied.
246    args = ()
247
248    # The style prefix in play. This is used by DynamicImage to figure
249    # out the prefix list to apply.
250    prefix = None
251
252    # True if lint is in use.
253    lint = False
254
255    def copy(self, **kwargs):
256        """
257        Returns a copy of this object with the various fields set to the
258        values they were given in kwargs.
259        """
260
261        rv = DisplayableArguments()
262        rv.__dict__.update(self.__dict__)
263        rv.__dict__.update(kwargs)
264
265        return rv
266
267    def extraneous(self):
268        if renpy.config.developer and renpy.config.report_extraneous_attributes:
269            raise Exception("Image '{}' does not accept attributes '{}'.".format(
270                " ".join(self.name),
271                " ".join(self.args),
272                ))
273
274
275default_style = renpy.style.Style("default")
276
277
278class Displayable(renpy.object.Object):
279    """
280    The base class for every object in Ren'Py that can be
281    displayed to the screen.
282
283    Drawables will be serialized to a savegame file. Therefore, they
284    shouldn't store non-serializable things (like pygame surfaces) in
285    their fields.
286    """
287
288    # Some invariants about method call order:
289    #
290    # per_interact is called before render.
291    # render is called before event.
292    #
293    # get_placement can be called at any time, so can't
294    # assume anything.
295
296    # If True this displayable can accept focus.
297    # If False, it can't, but it keeps its place in the focus order.
298    # If None, it does not have a place in the focus order.
299    focusable = None
300
301    # This is the focus named assigned by the focus code.
302    full_focus_name = None
303
304    # A role ('selected_' or '' that prefixes the style).
305    role = ''
306
307    # The event we'll pass on to our parent transform.
308    transform_event = None
309
310    # Can we change our look in response to transform_events?
311    transform_event_responder = False
312
313    # The main displayable, if this displayable is the root of a composite
314    # displayable. (This is used by SL to figure out where to add children
315    # to.) If None, it is itself.
316    _main = None
317
318    # A list of the children that make up this composite displayable.
319    _composite_parts = [ ]
320
321    # The location the displayable was created at, if known.
322    _location = None
323
324    # Does this displayable use the scope?
325    _uses_scope = False
326
327    # Arguments supplied to this displayable.
328    _args = DisplayableArguments()
329
330    # Set to true of the displayable is duplicatable (has a non-trivial
331    # duplicate method), or one of its children is.
332    _duplicatable = False
333
334    # Does this displayable require clipping?
335    _clipping = False
336
337    # Does this displayable have a tooltip?
338    _tooltip = None
339
340    def __ne__(self, o):
341        return not (self == o)
342
343    def __init__(self, focus=None, default=False, style='default', _args=None, tooltip=None, default_focus=False, **properties):
344
345        global default_style
346
347        if (style == "default") and (not properties):
348            self.style = default_style
349        else:
350            self.style = renpy.style.Style(style, properties) # @UndefinedVariable
351
352        self.focus_name = focus
353        self.default = default or default_focus
354        self._tooltip = tooltip
355
356        if _args is not None:
357            self._args = _args
358
359    def _copy(self, args=None):
360        """
361        Makes a shallow copy of the displayable. If `args` is provided,
362        replaces the arguments with the stored copy.
363        """
364
365        rv = copy.copy(self)
366
367        if args is not None:
368            rv._args = args
369
370        return rv
371
372    def _duplicate(self, args):
373        """
374        Makes a duplicate copy of the following kids of displayables:
375
376        * Displayables that can accept arguments.
377        * Displayables that maintain state that should be reset before being
378          shown to the user.
379        * Containers that contain (including transitively) one of the other
380          kinds of displayables.
381
382        Displayables that contain state that can be manipulated by the user
383        are never copied.
384
385        This should call _unique on children that have been copied before
386        setting its own _duplicatable flag.
387        """
388
389        if args and args.args:
390            args.extraneous()
391
392        return self
393
394    def _get_tooltip(self):
395        """
396        Returns the tooltip of this displayable.
397        """
398
399        return self._tooltip
400
401    def _in_current_store(self):
402        """
403        Returns a version of this displayable that will not change as it is
404        rendered.
405        """
406
407        return self
408
409    def _unique(self):
410        """
411        This is called when a displayable is "born" unique, which occurs
412        when there is only a single reference to it. What it does is to
413        manage the _duplicatable flag - setting it false unless one of
414        the displayable's children happens to be duplicatable.
415        """
416
417        return
418
419    def parameterize(self, name, parameters):
420        """
421        Obsolete alias for _duplicate.
422        """
423
424        a = self._args.copy(name=name, args=parameters)
425        return self._duplicate(a)
426
427    def _equals(self, o):
428        """
429        This is a utility method that can be called by a Displayable's
430        __eq__ method, to compare displayables for type and displayable
431        component equality.
432        """
433
434        if type(self) is not type(o):
435            return False
436
437        if self.focus_name != o.focus_name:
438            return False
439
440        if self.style != o.style:
441            return False
442
443        if self.default != o.default:
444            return False
445
446        return True
447
448    def __unicode__(self):
449        return self.__class__.__name__
450
451    def __str__(self):
452        return self.__class__.__name__
453
454    def __repr__(self):
455        return "<{} at {:x}>".format(str(self), id(self))
456
457    def find_focusable(self, callback, focus_name):
458
459        focus_name = self.focus_name or focus_name
460
461        if self.focusable:
462            callback(self, focus_name)
463        elif self.focusable is not None:
464            callback(None, focus_name)
465
466        for i in self.visit():
467            if i is None:
468                continue
469
470            i.find_focusable(callback, focus_name)
471
472    def focus(self, default=False):
473        """
474        Called to indicate that this widget has the focus.
475        """
476
477        self.set_style_prefix(self.role + "hover_", True)
478
479        if not default:
480            renpy.exports.play(self.style.hover_sound)
481
482    def unfocus(self, default=False):
483        """
484        Called to indicate that this widget has become unfocused.
485        """
486
487        self.set_style_prefix(self.role + "idle_", True)
488
489    def is_focused(self):
490
491        if renpy.display.focus.grab and renpy.display.focus.grab is not self:
492            return
493
494        return renpy.game.context().scene_lists.focused is self
495
496    def set_style_prefix(self, prefix, root):
497        """
498        Called to set the style prefix of this widget and its child
499        widgets, if any.
500
501        `root` - True if this is the root of a style tree, False if this
502        has been passed on to a child.
503        """
504
505        if prefix == self.style.prefix:
506            return
507
508        self.style.set_prefix(prefix)
509        renpy.display.render.redraw(self, 0)
510
511    def render(self, width, height, st, at):
512        """
513        Called to display this displayable. This is called with width
514        and height parameters, which give the largest width and height
515        that this drawable can be drawn to without overflowing some
516        bounding box. It's also given two times. It returns a Surface
517        that is the current image of this drawable.
518
519        @param st: The time since this widget was first shown, in seconds.
520        @param at: The time since a similarly named widget was first shown,
521        in seconds.
522        """
523
524        raise Exception("Render not implemented.")
525
526    def event(self, ev, x, y, st):
527        """
528        Called to report than an event has occured. Ev is the raw
529        pygame event object representing that event. If the event
530        involves the mouse, x and y are the translation of the event
531        into the coordinates of this displayable. st is the time this
532        widget has been shown for.
533
534        @returns A value that should be returned from Interact, or None if
535        no value is appropriate.
536        """
537
538        return None
539
540    def get_placement(self):
541        """
542        Returns a style object containing placement information for
543        this Displayable. Children are expected to overload this
544        to return something more sensible.
545        """
546
547        return self.style.get_placement()
548
549    def visit_all(self, callback, seen=None):
550        """
551        Calls the callback on this displayable, and then on all children
552        of this displayable.
553        """
554
555        if seen is None:
556            seen = set()
557
558        for d in self.visit():
559
560            if d is None:
561                continue
562
563            id_d = id(d)
564            if id_d in seen:
565                continue
566
567            seen.add(id_d)
568            d.visit_all(callback, seen)
569
570        callback(self)
571
572    def visit(self):
573        """
574        Called to ask the displayable to return a list of its children
575        (including children taken from styles). For convenience, this
576        list may also include None values.
577        """
578
579        return [ ]
580
581    def per_interact(self):
582        """
583        Called once per widget per interaction.
584        """
585
586        return None
587
588    def predict_one(self):
589        """
590        Called to ask this displayable to call the callback with all
591        the images it may want to load.
592        """
593
594        return
595
596    def predict_one_action(self):
597        """
598        Called to ask this displayable to cause image prediction
599        to occur for images that may be loaded by its actions.
600        """
601
602        return
603
604    def place(self, dest, x, y, width, height, surf, main=True):
605        """
606        This places a render (which must be of this displayable)
607        within a bounding area. Returns an (x, y) tuple giving the location
608        the displayable was placed at.
609
610        `dest`
611            If not None, the `surf` will be blitted to `dest` at the
612            computed coordinates.
613
614        `x`, `y`, `width`, `height`
615            The bounding area.
616
617        `surf`
618            The render to place.
619
620        `main`
621            This is passed to Render.blit().
622        """
623
624        placement = self.get_placement()
625        subpixel = placement[6]
626
627        xpos, ypos = place(width, height, surf.width, surf.height, placement)
628
629        xpos += x
630        ypos += y
631
632        pos = (xpos, ypos)
633
634        if dest is not None:
635            if subpixel:
636                dest.subpixel_blit(surf, pos, main, main, None)
637            else:
638                dest.blit(surf, pos, main, main, None)
639
640        return pos
641
642    def set_transform_event(self, event):
643        """
644        Sets the transform event of this displayable to event.
645        """
646
647        if event == self.transform_event:
648            return
649
650        self.transform_event = event
651
652        if self.transform_event_responder:
653            renpy.display.render.redraw(self, 0)
654
655    def _handles_event(self, event):
656        """
657        Returns True if the displayable handles event, False otherwise.
658        """
659
660        return False
661
662    def _hide(self, st, at, kind):
663        """
664        Returns None if this displayable is ready to be hidden, or
665        a replacement displayable if it doesn't want to be hidden
666        quite yet. Kind is either "hide" or "replaced".
667        """
668
669        return None
670
671    def _show(self):
672        """
673        No longer used.
674        """
675
676    def _target(self):
677        """
678        If this displayable is part of a chain of one or more references,
679        returns the ultimate target of those references. Otherwise, returns
680        the displayable.
681        """
682
683        return self
684
685    def _change_transform_child(self, child):
686        """
687        If this is a transform, makes a copy of the transform and sets
688        the child of the innermost transform to this. Otherwise,
689        simply returns child.
690        """
691
692        return child
693
694    def _clear(self):
695        """
696        Clears out the children of this displayable, if any.
697        """
698
699        return
700
701    def _tts_common(self, default_alt=None, reverse=False):
702
703        rv = [ ]
704
705        if reverse:
706            order = 1
707        else:
708            order = -1
709
710        speech = ""
711
712        for i in self.visit()[::order]:
713            if i is not None:
714                speech = i._tts()
715
716                if speech.strip():
717                    rv.append(speech)
718
719                    if isinstance(speech, renpy.display.tts.TTSDone):
720                        break
721
722        rv = ": ".join(rv)
723        rv = rv.replace("::", ":")
724        rv = rv.replace(": :", ":")
725
726        alt = self.style.alt
727
728        if alt is None:
729            alt = default_alt
730
731        if alt is not None:
732            rv = renpy.substitutions.substitute(alt, scope={ "text" : rv })[0]
733
734        rv = type(speech)(rv)
735
736        return rv
737
738    def _tts(self):
739        """
740        Returns the self-voicing text of this displayable and all of its
741        children that cannot take focus. If the displayable can take focus,
742        returns the empty string.
743        """
744
745        return self._tts_common()
746
747    def _tts_all(self):
748        """
749        Returns the self-voicing text of this displayable and all of its
750        children that cannot take focus.
751        """
752        return self._tts_common()
753
754
755class SceneListEntry(renpy.object.Object):
756    """
757    Represents a scene list entry. Since this was replacing a tuple,
758    it should be treated as immutable after its initial creation.
759    """
760
761    def __init__(self, tag, zorder, show_time, animation_time, displayable, name):
762        self.tag = tag
763        self.zorder = zorder
764        self.show_time = show_time
765        self.animation_time = animation_time
766        self.displayable = displayable
767        self.name = name
768
769    def __iter__(self):
770        return iter((self.tag, self.zorder, self.show_time, self.animation_time, self.displayable))
771
772    def __getitem__(self, index):
773        return (self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)[index]
774
775    def __repr__(self):
776        return "<SLE: %r %r %r>" % (self.tag, self.name, self.displayable)
777
778    def copy(self):
779        return SceneListEntry(
780            self.tag,
781            self.zorder,
782            self.show_time,
783            self.animation_time,
784            self.displayable,
785            self.name)
786
787    def update_time(self, time):
788
789        rv = self
790
791        if self.show_time is None or self.animation_time is None:
792            rv = self.copy()
793            rv.show_time = rv.show_time or time
794            rv.animation_time = rv.animation_time or time
795
796        return rv
797
798
799class SceneLists(renpy.object.Object):
800    """
801    This stores the current scene lists that are being used to display
802    things to the user.
803    """
804
805    __version__ = 7
806
807    def after_setstate(self):
808
809        self.camera_list = getattr(self, "camera_list", { })
810        self.camera_transform = getattr(self, "camera_transform", { })
811
812        for i in renpy.config.layers + renpy.config.top_layers:
813            if i not in self.layers:
814                self.layers[i] = [ ]
815                self.at_list[i] = { }
816                self.layer_at_list[i] = (None, [ ])
817
818            if i not in self.camera_list:
819                self.camera_list[i] = (None, [ ])
820
821    def after_upgrade(self, version):
822
823        if version < 1:
824
825            self.at_list = { }
826            self.layer_at_list = { }
827
828            for i in renpy.config.layers + renpy.config.top_layers:
829                self.at_list[i] = { }
830                self.layer_at_list[i] = (None, [ ])
831
832        if version < 3:
833            self.shown_window = False
834
835        if version < 4:
836            for k in self.layers:
837                self.layers[k] = [ SceneListEntry(*(i + (None,))) for i in self.layers[k] ]
838
839            self.additional_transient = [ ]
840
841        if version < 5:
842            self.drag_group = None
843
844        if version < 6:
845            self.shown = self.image_predict_info
846
847        if version < 7:
848            self.layer_transform = { }
849
850    def __init__(self, oldsl, shown):
851
852        super(SceneLists, self).__init__()
853
854        # Has a window been shown as part of these scene lists?
855        self.shown_window = False
856
857        # A map from layer name -> list(SceneListEntry)
858        self.layers = { }
859
860        # A map from layer name -> tag -> at_list associated with that tag.
861        self.at_list = { }
862
863        # A map from layer to (start time, at_list), where the at list has
864        # been applied to the layer as a whole.
865        self.layer_at_list = { }
866
867        # The camera list, which is similar to the layer at list but is not
868        # cleared during the scene statement.
869        self.camera_list = { }
870
871        # The current shown images,
872        self.shown = shown
873
874        # A list of (layer, tag) pairs that are considered to be
875        # transient.
876        self.additional_transient = [ ]
877
878        # Either None, or a DragGroup that's used as the default for
879        # drags with names.
880        self.drag_group = None
881
882        # A map from a layer to the transform that applies to that
883        # layer.
884        self.layer_transform = { }
885
886        # Same thing, but for the camera transform.
887        self.camera_transform = { }
888
889        if oldsl:
890
891            for i in renpy.config.layers + renpy.config.top_layers:
892
893                try:
894                    self.layers[i] = oldsl.layers[i][:]
895                except KeyError:
896                    self.layers[i] = [ ]
897
898                if i in oldsl.at_list:
899                    self.at_list[i] = oldsl.at_list[i].copy()
900                    self.layer_at_list[i] = oldsl.layer_at_list[i]
901                    self.camera_list[i] = oldsl.camera_list[i]
902                else:
903                    self.at_list[i] = { }
904                    self.layer_at_list[i] = (None, [ ])
905                    self.camera_list[i] = (None, [ ])
906
907            for i in renpy.config.overlay_layers:
908                self.clear(i)
909
910            self.replace_transient(prefix=None)
911
912            self.focused = None
913
914            self.drag_group = oldsl.drag_group
915
916            self.layer_transform.update(oldsl.layer_transform)
917            self.camera_transform.update(oldsl.camera_transform)
918
919        else:
920            for i in renpy.config.layers + renpy.config.top_layers:
921                self.layers[i] = [ ]
922                self.at_list[i] = { }
923                self.layer_at_list[i] = (None, [ ])
924                self.camera_list[i] = (None, [ ])
925
926            self.music = None
927            self.focused = None
928
929    def replace_transient(self, prefix="hide"):
930        """
931        Replaces the contents of the transient display list with
932        a copy of the master display list. This is used after a
933        scene is displayed to get rid of transitions and interface
934        elements.
935
936        `prefix`
937            The prefix/event to use. Set this to None to prevent the hide
938            from happening.
939        """
940
941        for i in renpy.config.transient_layers:
942            self.clear(i, True)
943
944        for layer, tag in self.additional_transient:
945            self.remove(layer, tag, prefix=prefix)
946
947        self.additional_transient = [ ]
948
949    def transient_is_empty(self):
950        """
951        This returns True if all transient layers are empty. This is
952        used by the rollback code, as we can't start a new rollback
953        if there is something in a transient layer (as things in the
954        transient layer may contain objects that cannot be pickled,
955        like lambdas.)
956        """
957
958        for i in renpy.config.transient_layers:
959            if self.layers[i]:
960                return False
961
962        return True
963
964    def transform_state(self, old_thing, new_thing, execution=False):
965        """
966        If the old thing is a transform, then move the state of that transform
967        to the new thing.
968        """
969
970        if old_thing is None:
971            return new_thing
972
973        # Don't bother wrapping screens, as they can't be transformed.
974        if isinstance(new_thing, renpy.display.screen.ScreenDisplayable):
975            return new_thing
976
977        if renpy.config.take_state_from_target:
978            old_transform = old_thing._target()
979        else:
980            old_transform = old_thing
981
982        if not isinstance(old_transform, renpy.display.motion.Transform):
983            return new_thing
984
985        if renpy.config.take_state_from_target:
986            new_transform = new_thing._target()
987        else:
988            new_transform = new_thing
989
990        if not isinstance(new_transform, renpy.display.motion.Transform):
991            new_thing = new_transform = renpy.display.motion.Transform(child=new_thing)
992
993        new_transform.take_state(old_transform)
994
995        if execution:
996            new_transform.take_execution_state(old_transform)
997
998        return new_thing
999
1000    def find_index(self, layer, tag, zorder, behind):
1001        """
1002        This finds the spot in the named layer where we should insert the
1003        displayable. It returns two things: an index at which the new thing
1004        should be added, and an index at which the old thing should be hidden.
1005        (Note that the indexes are relative to the current state of the list,
1006        which may change on an add or remove.)
1007        """
1008
1009        add_index = None
1010        remove_index = None
1011
1012        for i, sle in enumerate(self.layers[layer]):
1013
1014            if remove_index is None:
1015                if (sle.tag and sle.tag == tag) or sle.displayable == tag:
1016                    remove_index = i
1017
1018                    if zorder is None:
1019                        zorder = sle.zorder
1020
1021        if zorder is None:
1022            zorder = renpy.config.tag_zorder.get(tag, 0)
1023
1024        for i, sle in enumerate(self.layers[layer]):
1025
1026            if add_index is None:
1027
1028                if sle.zorder == zorder:
1029                    if sle.tag and (sle.tag == tag or sle.tag in behind):
1030                        add_index = i
1031
1032                elif sle.zorder > zorder:
1033                    add_index = i
1034
1035        if add_index is None:
1036            add_index = len(self.layers[layer])
1037
1038        return add_index, remove_index, zorder
1039
1040    def add(self,
1041            layer,
1042            thing,
1043            key=None,
1044            zorder=0,
1045            behind=[ ],
1046            at_list=[ ],
1047            name=None,
1048            atl=None,
1049            default_transform=None,
1050            transient=False,
1051            keep_st=False):
1052        """
1053        Adds something to this scene list. Some of these names are quite a bit
1054        out of date.
1055
1056        `thing` - The displayable to add.
1057
1058        `key` - A string giving the tag associated with this thing.
1059
1060        `zorder` - Where to place this thing in the zorder, an integer
1061        A greater value means closer to the user.
1062
1063        `behind` - A list of tags to place the thing behind.
1064
1065        `at_list` - The at_list associated with this
1066        displayable. Counterintuitively, this is not actually
1067        applied, but merely stored for future use.
1068
1069        `name` - The full name of the image being displayed. This is used for
1070        image lookup.
1071
1072        `atl` - If not None, an atl block applied to the thing. (This actually is
1073        applied here.)
1074
1075        `default_transform` - The default transform that is used to initialized
1076        the values in the other transforms.
1077
1078        `keep_st`
1079            If true, we preserve the shown time of a replaced displayable.
1080        """
1081
1082        if not isinstance(thing, Displayable):
1083            raise Exception("Attempting to show something that isn't a displayable:" + repr(thing))
1084
1085        if layer not in self.layers:
1086            raise Exception("Trying to add something to non-existent layer '%s'." % layer)
1087
1088        if key:
1089            self.remove_hide_replaced(layer, key)
1090            self.at_list[layer][key] = at_list
1091
1092        if key and name:
1093            self.shown.predict_show(layer, name)
1094
1095        if transient:
1096            self.additional_transient.append((layer, key))
1097
1098        l = self.layers[layer]
1099
1100        if atl:
1101            thing = renpy.display.motion.ATLTransform(atl, child=thing)
1102
1103        add_index, remove_index, zorder = self.find_index(layer, key, zorder, behind)
1104
1105        at = None
1106        st = None
1107
1108        if remove_index is not None:
1109            sle = l[remove_index]
1110            old = sle.displayable
1111
1112            at = sle.animation_time
1113
1114            if keep_st:
1115                st = sle.show_time
1116
1117            if not self.hide_or_replace(layer, remove_index, "replaced"):
1118                if add_index > remove_index:
1119                    add_index -= 1
1120
1121            if (not atl and
1122                    not at_list and
1123                    renpy.config.keep_running_transform and
1124                    isinstance(old, renpy.display.motion.Transform)):
1125
1126                thing = sle.displayable._change_transform_child(thing)
1127
1128            else:
1129
1130                thing = self.transform_state(old, thing)
1131
1132            thing.set_transform_event("replace")
1133
1134        else:
1135
1136            if not isinstance(thing, renpy.display.motion.Transform):
1137                thing = self.transform_state(default_transform, thing)
1138
1139            thing.set_transform_event("show")
1140
1141        if add_index is not None:
1142            sle = SceneListEntry(key, zorder, st, at, thing, name)
1143            l.insert(add_index, sle)
1144
1145        # By walking the tree of displayables we allow the displayables to
1146        # capture the current state.
1147        thing.visit_all(lambda d : None)
1148
1149    def hide_or_replace(self, layer, index, prefix):
1150        """
1151        Hides or replaces the scene list entry at the given
1152        index. `prefix` is a prefix that is used if the entry
1153        decides it doesn't want to be hidden quite yet.
1154
1155        Returns True if the displayable is kept, False if it is removed.
1156        """
1157
1158        if index is None:
1159            return False
1160
1161        l = self.layers[layer]
1162        oldsle = l[index]
1163
1164        now = get_time()
1165
1166        st = oldsle.show_time or now
1167        at = oldsle.animation_time or now
1168
1169        if renpy.config.fast_unhandled_event:
1170            if not oldsle.displayable._handles_event(prefix):
1171                prefix = None
1172
1173        if (prefix is not None) and oldsle.tag:
1174
1175            d = oldsle.displayable._in_current_store()._hide(now - st, now - at, prefix)
1176
1177            # _hide can mutate the layers, so we need to recompute
1178            # index.
1179            index = l.index(oldsle)
1180
1181            if d is not None:
1182
1183                sle = SceneListEntry(
1184                    prefix + "$" + oldsle.tag,
1185                    oldsle.zorder,
1186                    st,
1187                    at,
1188                    d,
1189                    None)
1190
1191                l[index] = sle
1192
1193                return True
1194
1195        l.pop(index)
1196
1197        return False
1198
1199    def get_all_displayables(self, current=False):
1200        """
1201        Gets all displayables reachable from this scene list.
1202
1203        `current`
1204            If true, only returns displayables that are not in the process
1205            of being hidden.
1206        """
1207
1208        rv = [ ]
1209        for l in self.layers.values():
1210            for sle in l:
1211
1212                if current and sle.tag and ("$" in sle.tag):
1213                    continue
1214
1215                rv.append(sle.displayable)
1216
1217        return rv
1218
1219    def remove_above(self, layer, thing):
1220        """
1221        Removes everything on the layer that is closer to the user
1222        than thing, which may be either a tag or a displayable. Thing must
1223        be displayed, or everything will be removed.
1224        """
1225
1226        for i in range(len(self.layers[layer]) - 1, -1, -1):
1227
1228            sle = self.layers[layer][i]
1229
1230            if thing:
1231                if sle.tag == thing or sle.displayable == thing:
1232                    break
1233
1234            if sle.tag and "$" in sle.tag:
1235                continue
1236
1237            self.hide_or_replace(layer, i, "hide")
1238
1239    def remove(self, layer, thing, prefix="hide"):
1240        """
1241        Thing is either a key or a displayable. This iterates through the
1242        named layer, searching for entries matching the thing.
1243        When they are found, they are removed from the displaylist.
1244
1245        It's not an error to remove something that isn't in the layer in
1246        the first place.
1247        """
1248
1249        if layer not in self.layers:
1250            raise Exception("Trying to remove something from non-existent layer '%s'." % layer)
1251
1252        _add_index, remove_index, _zorder = self.find_index(layer, thing, 0, [ ])
1253
1254        if remove_index is not None:
1255            tag = self.layers[layer][remove_index].tag
1256
1257            if tag:
1258                self.shown.predict_hide(layer, (tag,))
1259                self.at_list[layer].pop(tag, None)
1260
1261            self.hide_or_replace(layer, remove_index, prefix)
1262
1263    def clear(self, layer, hide=False):
1264        """
1265        Clears the named layer, making it empty.
1266
1267        If hide is True, then objects are hidden. Otherwise, they are
1268        totally wiped out.
1269        """
1270
1271        if layer not in self.layers:
1272            return
1273
1274        if not hide:
1275            self.layers[layer][:] = [ ]
1276
1277        else:
1278
1279            # Have to iterate in reverse order, since otherwise
1280            # the indexes might change.
1281            for i in range(len(self.layers[layer]) - 1, -1, -1):
1282                self.hide_or_replace(layer, i, hide)
1283
1284        self.at_list[layer].clear()
1285        self.shown.predict_scene(layer)
1286
1287        if renpy.config.scene_clears_layer_at_list:
1288            self.layer_at_list[layer] = (None, [ ])
1289
1290    def set_layer_at_list(self, layer, at_list, reset=True, camera=False):
1291
1292        if camera:
1293            self.camera_list[layer] = (None, list(at_list))
1294        else:
1295            self.layer_at_list[layer] = (None, list(at_list))
1296
1297        if reset:
1298            self.layer_transform[layer] = None
1299
1300    def set_times(self, time):
1301        """
1302        This finds entries with a time of None, and replaces that
1303        time with the given time.
1304        """
1305
1306        for l, (t, ll) in list(self.camera_list.items()):
1307            self.camera_list[l] = (t or time, ll)
1308
1309        for l, (t, ll) in list(self.layer_at_list.items()):
1310            self.layer_at_list[l] = (t or time, ll)
1311
1312        for l, ll in self.layers.items():
1313            self.layers[l][:] = [ i.update_time(time) for i in ll ]
1314
1315    def showing(self, layer, name):
1316        """
1317        Returns true if something with the prefix of the given name
1318        is found in the scene list.
1319        """
1320
1321        return self.shown.showing(layer, name)
1322
1323    def get_showing_tags(self, layer):
1324        return self.shown.get_showing_tags(layer)
1325
1326    def get_sorted_tags(self, layer):
1327        rv = [ ]
1328
1329        for sle in self.layers[layer]:
1330            if not sle.tag:
1331                continue
1332
1333            if "$" in sle.tag:
1334                continue
1335
1336            rv.append(sle.tag)
1337
1338        return rv
1339
1340    def make_layer(self, layer, properties):
1341        """
1342        Creates a Fixed with the given layer name and scene_list.
1343        """
1344
1345        rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **properties)
1346        rv.append_scene_list(self.layers[layer])
1347        rv.layer_name = layer
1348        rv._duplicatable = False
1349        rv._layer_at_list = self.layer_at_list[layer]
1350        rv._camera_list = self.camera_list[layer]
1351
1352        return rv
1353
1354    def transform_layer(self, layer, d, layer_at_list=None, camera_list=None):
1355        """
1356        When `d` is a layer created with make_layer, returns `d` with the
1357        various at_list transforms applied to it.
1358        """
1359
1360        if layer_at_list is None:
1361            layer_at_list = self.layer_at_list[layer]
1362        if camera_list is None:
1363            camera_list = self.camera_list[layer]
1364
1365        rv = d
1366
1367        # Layer at list.
1368
1369        time, at_list = layer_at_list
1370
1371        old_transform = self.layer_transform.get(layer, None)
1372        new_transform = None
1373
1374        if at_list:
1375
1376            for a in at_list:
1377
1378                if isinstance(a, renpy.display.motion.Transform):
1379                    rv = a(child=rv)
1380                    new_transform = rv
1381                else:
1382                    rv = a(rv)
1383
1384            if (new_transform is not None) and (renpy.config.keep_show_layer_state):
1385                self.transform_state(old_transform, new_transform, execution=True)
1386
1387            f = renpy.display.layout.MultiBox(layout='fixed')
1388            f.add(rv, time, time)
1389            f.layer_name = layer
1390
1391            rv = f
1392
1393        self.layer_transform[layer] = new_transform
1394
1395        # Camera list.
1396
1397        time, at_list = camera_list
1398
1399        old_transform = self.camera_transform.get(layer, None)
1400        new_transform = None
1401
1402        if at_list:
1403
1404            for a in at_list:
1405
1406                if isinstance(a, renpy.display.motion.Transform):
1407                    rv = a(child=rv)
1408                    new_transform = rv
1409                else:
1410                    rv = a(rv)
1411
1412            if (new_transform is not None):
1413                self.transform_state(old_transform, new_transform, execution=True)
1414
1415            f = renpy.display.layout.MultiBox(layout='fixed')
1416            f.add(rv, time, time)
1417            f.layer_name = layer
1418
1419            rv = f
1420
1421        self.camera_transform[layer] = new_transform
1422
1423        return rv
1424
1425    def remove_hide_replaced(self, layer, tag):
1426        """
1427        Removes things that are hiding or replaced, that have the given
1428        tag.
1429        """
1430
1431        hide_tag = "hide$" + tag
1432        replaced_tag = "replaced$" + tag
1433
1434        l = self.layers[layer]
1435        self.layers[layer][:] = [ i for i in l if i.tag != hide_tag and i.tag != replaced_tag ]
1436
1437    def remove_hidden(self):
1438        """
1439        Goes through all of the layers, and removes things that are
1440        hidden and are no longer being kept alive by their hide
1441        methods.
1442        """
1443
1444        now = get_time()
1445
1446        for l in self.layers:
1447            newl = [ ]
1448
1449            for sle in self.layers[l]:
1450
1451                if sle.tag:
1452
1453                    if sle.tag.startswith("hide$"):
1454                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "hide")
1455                        if not d:
1456                            continue
1457
1458                    elif sle.tag.startswith("replaced$"):
1459                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "replaced")
1460                        if not d:
1461                            continue
1462
1463                newl.append(sle)
1464
1465            self.layers[l][:] = newl
1466
1467    def remove_all_hidden(self):
1468        """
1469        Removes everything hidden, even if it's not time yet. (Used when making a rollback copy).
1470        """
1471
1472        for l in self.layers:
1473            newl = [ ]
1474
1475            for sle in self.layers[l]:
1476
1477                if sle.tag:
1478
1479                    if "$" in sle.tag:
1480                        continue
1481
1482                newl.append(sle)
1483
1484            self.layers[l][:] = newl
1485
1486    def get_displayable_by_tag(self, layer, tag):
1487        """
1488        Returns the displayable on the layer with the given tag, or None
1489        if no such displayable exists. Note that this will usually return
1490        a Transform.
1491        """
1492
1493        if layer not in self.layers:
1494            raise Exception("Unknown layer %r." % layer)
1495
1496        for sle in self.layers[layer]:
1497            if sle.tag == tag:
1498                return sle.displayable
1499
1500        return None
1501
1502    def get_displayable_by_name(self, layer, name):
1503        """
1504        Returns the displayable on the layer with the given name, or None
1505        if no such displayable exists. Note that this will usually return
1506        a Transform.
1507        """
1508
1509        if layer not in self.layers:
1510            raise Exception("Unknown layer %r." % layer)
1511
1512        for sle in self.layers[layer]:
1513            if sle.name == name:
1514                return sle.displayable
1515
1516        return None
1517
1518    def get_image_bounds(self, layer, tag, width, height):
1519        """
1520        Implements renpy.get_image_bounds().
1521        """
1522
1523        if layer not in self.layers:
1524            raise Exception("Unknown layer %r." % layer)
1525
1526        for sle in self.layers[layer]:
1527            if sle.tag == tag:
1528                break
1529        else:
1530            return None
1531
1532        now = get_time()
1533
1534        if sle.show_time is not None:
1535            st = now - sle.show_time
1536        else:
1537            st = 0
1538
1539        if sle.animation_time is not None:
1540            at = now - sle.animation_time
1541        else:
1542            at = 0
1543
1544        surf = renpy.display.render.render_for_size(sle.displayable, width, height, st, at)
1545
1546        sw = surf.width
1547        sh = surf.height
1548
1549        x, y = place(width, height, sw, sh, sle.displayable.get_placement())
1550
1551        return (x, y, sw, sh)
1552
1553    def get_zorder_list(self, layer):
1554        """
1555        Returns a list of (tag, zorder) pairs.
1556        """
1557
1558        rv = [ ]
1559
1560        for sle in self.layers.get(layer, [ ]):
1561
1562            if sle.tag is None:
1563                continue
1564            if "$" in sle.tag:
1565                continue
1566
1567            rv.append((sle.tag, sle.zorder))
1568
1569        return rv
1570
1571    def change_zorder(self, layer, tag, zorder):
1572        """
1573        Changes the zorder for tag on layer.
1574        """
1575
1576        sl = self.layers.get(layer, [ ])
1577        for sle in sl:
1578
1579            if sle.tag == tag:
1580                sle.zorder = zorder
1581
1582        sl.sort(key=lambda sle : sle.zorder)
1583
1584
1585def scene_lists(index=-1):
1586    """
1587    Returns either the current scenelists object, or the one for the
1588    context at the given index.
1589    """
1590
1591    return renpy.game.context(index).scene_lists
1592
1593
1594class MouseMove(object):
1595    """
1596    This contains information about the current mouse move.
1597    """
1598
1599    def __init__(self, x, y, duration):
1600        self.start = get_time()
1601
1602        if duration is not None:
1603            self.duration = duration
1604        else:
1605            self.duration = 0
1606
1607        self.start_x, self.start_y = renpy.display.draw.get_mouse_pos()
1608
1609        self.end_x = x
1610        self.end_y = y
1611
1612    def perform(self):
1613        """
1614        Performs the mouse move. Returns True if this should be called
1615        again, or False if the move has finished.
1616        """
1617
1618        elapsed = get_time() - self.start
1619
1620        if elapsed >= self.duration:
1621            renpy.display.draw.set_mouse_pos(self.end_x, self.end_y)
1622            return False
1623
1624        done = 1.0 * elapsed / self.duration
1625
1626        x = int(self.start_x + done * (self.end_x - self.start_x))
1627        y = int(self.start_y + done * (self.end_y - self.start_y))
1628
1629        renpy.display.draw.set_mouse_pos(x, y)
1630        return True
1631
1632
1633def get_safe_mode():
1634    """
1635    Returns true if we should go into safe mode.
1636    """
1637
1638    if renpy.safe_mode_checked:
1639        return False
1640
1641    if getattr(renpy.game.args, "safe_mode", False):
1642        return True
1643
1644    try:
1645        if renpy.windows:
1646            import ctypes
1647
1648            VK_SHIFT = 0x10
1649
1650            ctypes.windll.user32.GetKeyState.restype = ctypes.c_ushort
1651            if ctypes.windll.user32.GetKeyState(VK_SHIFT) & 0x8000:
1652                return True
1653            else:
1654                return False
1655
1656        # Safe mode doesn't work on other platforms.
1657        return False
1658
1659    except:
1660        return False
1661
1662
1663class Renderer(object):
1664    """
1665    A Renderer (also known as a draw object) is responsible for drawing a
1666    tree of displayables to the window. It also provides other services that
1667    involved drawing and the SDL main window, as documented here.
1668
1669    A Renderer is responsible for updating the renpy.game.preferences.fullscreen
1670    and renpy.game.preferences.physical_size preferences, when these are
1671    changed from outside the game.
1672
1673    A renderer has an info dict, that contains the keys from pygame_sdl2.display.Info(),
1674    and then:
1675    - "renderer", the name of the Renderer.
1676    - "resizable", true if the window can be resized.
1677    - "additive", true if additive blendering is supported.
1678    - "models", true if model-based rendering is being used.
1679    """
1680
1681    def get_texture_size(self):
1682        """
1683        This returns a pair contining the total amount of memory consumed by
1684        textures, and a count of the number of textures that exist.
1685        """
1686
1687    def update(self, force=False):
1688        """
1689        This is called before a draw operation to check to see if the state of
1690        the draw objects needs to be updated after an external event has occured.
1691        Things that require draw updates might be:
1692
1693        * The window has changed its size.
1694        * The window has changed full-screen status.
1695        * `force` is given, which generally means that it's likely the GL
1696          context has become invalid.
1697
1698        After this has been called, the system should be in a good state for
1699        rendering.
1700
1701        Returns True if a redraw is required, False otherwise.
1702        """
1703
1704    def init(self, virtual_size):
1705        """
1706        This creates a renderer with the given `virtual_size`. It returns
1707        True of the renderer initialized correctly, False otherwise.
1708        """
1709
1710    def quit(self):
1711        """
1712        This shuts down the renderer until the next call to ``init``.
1713        """
1714
1715    def resize(self):
1716        """
1717        This is called to implement resizing and changing the fullscreen
1718        mode. It is expected to determine the size to use using
1719        renpy.game.preferences.physical_size, and the fullscreen mode
1720        using renpy.game.preferences.fullscreen.
1721        """
1722
1723    def can_block(self):
1724        """
1725        Returns True if we can block to wait for input, False if the screen
1726        needs to be immediately redrawn.
1727        """
1728
1729    def should_redraw(self, needs_redraw, first_pass, can_block):
1730        """
1731        Determines if the screen needs to be redrawn. Returns True if it
1732        does.
1733
1734        `needs_redraw`
1735            True if the needs_redraw flag is set.
1736
1737        `first_pass`
1738            True if this is the first pass through the interact loop.
1739
1740        `can_block`
1741            The value of self.can_block, from above.
1742        """
1743
1744    def mutated_surface(self, surf):
1745        """
1746        Called to indicated that `surf` has changed and textures based on
1747        it should not be used.
1748        """
1749
1750        if surf in self.texture_cache:
1751            del self.texture_cache[surf]
1752
1753    def load_texture(self, surf, transient=False):
1754        """
1755        Loads a surface into a texture.
1756
1757        `surf`
1758            The pygame.Surface to load.
1759
1760        `transient`
1761            True if the texture is unlikely to be used for more than a single
1762            frame.
1763        """
1764
1765    def ready_one_texture(self):
1766        """
1767        This is called in the main thread to indicate that one texture
1768        should be loaded into the GPU.
1769        """
1770
1771    def kill_textures(self):
1772        """
1773        Removes all cached textures, to free memory.
1774        """
1775
1776    def solid_texture(self, w, h, color):
1777        """
1778        This is called to create a (`w` x `h`) texture of a single
1779        color.
1780
1781        Returns the texture.
1782        """
1783
1784    def draw_screen(self, surftree, flip=True):
1785        """
1786        This draw the screen.
1787
1788        `surftree`
1789            A Render object (the root of a tree of Render objects) that
1790            will be drawn to the screen.
1791
1792        `flip`
1793            If True, the drawing will be presented to the user.
1794        """
1795
1796    def render_to_texture(self, what, alpha):
1797        """
1798        Converts `what`, a tree of Renders, to a texture of the same size.
1799
1800        `alpha`
1801            A hint as to if the texture should have an alpha channel.
1802        """
1803
1804    def is_pixel_opaque(self, what, x, y):
1805        """
1806        Returns true if the pixel is not 100% transparent.
1807
1808        `what`
1809            A tree of renders.
1810
1811        `x`, `y`
1812            The coordinates of the pixels to check.
1813        """
1814
1815    def get_half(self, what):
1816        """
1817        Gets a texture that is half the size of `what`, which may be
1818        a texture or a tree of Renders.
1819        """
1820
1821    def translate_point(self, x, y):
1822        """
1823        Translates (`x`, `y`) from physical to virtual coordinates.
1824        """
1825
1826    def untranslate_point(self, x, y):
1827        """
1828        Untranslates (`x`, `y`) from virtual to physical coordinates.
1829        """
1830
1831    def mouse_event(self, ev):
1832        """
1833        This translates the .pos field of `ev` from physical coordinates to
1834        virtual coordinates. Returns an (x, y) pait of virtual coordinates.
1835        """
1836
1837    def get_mouse_pos(self):
1838        """
1839        Returns the x and y coordinates of the mouse, in virtual coordinates.
1840        """
1841
1842    def set_mouse_pos(self, x, y):
1843        """
1844        Moves the mouse to the virtual coordinates `x` and `y`.
1845        """
1846
1847        x, y = self.untranslate_point(x, y)
1848        pygame.mouse.set_pos([x, y])
1849
1850    def screenshot(self, surftree):
1851        """
1852        This returns a pygame.Surface that is the result of rendering
1853        `surftree`, a tree of Renders.
1854        """
1855
1856    def event_peek_sleep(self):
1857        """
1858        On platforms where CPU usage is gated by the need to redraw, sleeps
1859        a short amount of time to keep the CPU idle.
1860        """
1861
1862    def get_physical_size(self):
1863        """
1864        Returns the physical size of the window, in physical pixels.
1865        """
1866
1867
1868# How long should we be in maximum framerate mode at the start of the game?
1869initial_maximum_framerate = 0.0
1870
1871
1872class Interface(object):
1873    """
1874    This represents the user interface that interacts with the user.
1875    It manages the Display objects that display things to the user, and
1876    also handles accepting and responding to user input.
1877
1878    @ivar display: The display that we used to display the screen.
1879
1880    @ivar profile_time: The time of the last profiling.
1881
1882    @ivar screenshot: A screenshot, or None if no screenshot has been
1883    taken.
1884
1885    @ivar old_scene: The last thing that was displayed to the screen.
1886
1887    @ivar transition: A map from layer name to the transition that will
1888    be applied the next time interact restarts.
1889
1890    @ivar transition_time: A map from layer name to the time the transition
1891    involving that layer started.
1892
1893    @ivar transition_from: A map from layer name to the scene that we're
1894    transitioning from on that layer.
1895
1896    @ivar suppress_transition: If True, then the next transition will not
1897    happen.
1898
1899    @ivar force_redraw: If True, a redraw is forced.
1900
1901    @ivar restart_interaction: If True, the current interaction will
1902    be restarted.
1903
1904    @ivar pushed_event: If not None, an event that was pushed back
1905    onto the stack.
1906
1907    @ivar mouse: The name of the mouse cursor to use during the current
1908    interaction.
1909
1910    @ivar ticks: The number of 20hz ticks.
1911
1912    @ivar frame_time: The time at which we began drawing this frame.
1913
1914    @ivar interact_time: The time of the start of the first frame of the current interact_core.
1915
1916    @ivar time_event: A singleton ignored event.
1917
1918    @ivar event_time: The time of the current event.
1919
1920    @ivar timeout_time: The time at which the timeout will occur.
1921    """
1922
1923    def __init__(self):
1924
1925        # PNG data and the surface for the current file screenshot.
1926        self.screenshot = None
1927        self.screenshot_surface = None
1928
1929        self.old_scene = { }
1930        self.transition = { }
1931        self.ongoing_transition = { }
1932        self.transition_time = { }
1933        self.transition_from = { }
1934        self.suppress_transition = False
1935        self.quick_quit = False
1936        self.force_redraw = False
1937        self.restart_interaction = False
1938        self.pushed_event = None
1939        self.ticks = 0
1940        self.mouse = 'default'
1941        self.timeout_time = None
1942        self.last_event = None
1943        self.current_context = None
1944        self.roll_forward = None
1945
1946        # Are we in fullscreen mode?
1947        self.fullscreen = False
1948
1949        # Things to be preloaded.
1950        self.preloads = [ ]
1951
1952        # The time at which this object was initialized.
1953        self.init_time = get_time()
1954
1955        # The time at which this draw occurs.
1956        self.frame_time = 0
1957
1958        # The time when this interaction occured.
1959        self.interact_time = None
1960
1961        # The time we last tried to quit.
1962        self.quit_time = 0
1963
1964        # Are we currently processing the quit event?
1965        self.in_quit_event = False
1966
1967        self.time_event = pygame.event.Event(TIMEEVENT, { "modal" : False })
1968        self.redraw_event = pygame.event.Event(REDRAW)
1969
1970        # Are we focused?
1971        self.mouse_focused = True
1972        self.keyboard_focused = True
1973
1974        # Properties for each layer.
1975        self.layer_properties = { }
1976
1977        # Have we shown the window this interaction?
1978        self.shown_window = False
1979
1980        # Should we ignore the rest of the current touch? Used to ignore the
1981        # rest of a mousepress after a longpress occurs.
1982        self.ignore_touch = False
1983
1984        # Should we clear the screenshot at the start of the next interaction?
1985        self.clear_screenshot = False
1986
1987        for layer in renpy.config.layers + renpy.config.top_layers:
1988            if layer in renpy.config.layer_clipping:
1989                x, y, w, h = renpy.config.layer_clipping[layer]
1990                self.layer_properties[layer] = dict(
1991                    xpos=x,
1992                    xanchor=0,
1993                    ypos=y,
1994                    yanchor=0,
1995                    xmaximum=w,
1996                    ymaximum=h,
1997                    xminimum=w,
1998                    yminimum=h,
1999                    clipping=True,
2000                    )
2001
2002            else:
2003                self.layer_properties[layer] = dict()
2004
2005        # A stack giving the values of self.transition and self.transition_time
2006        # for contexts outside the current one. This is used to restore those
2007        # in the case where nothing has changed in the new context.
2008        self.transition_info_stack = [ ]
2009
2010        # The time when the event was dispatched.
2011        self.event_time = 0
2012
2013        # The time we saw the last mouse event.
2014        self.mouse_event_time = None
2015
2016        # Should we show the mouse?
2017        self.show_mouse = True
2018
2019        # Should we reset the display?
2020        self.display_reset = False
2021
2022        # Should we profile the next frame?
2023        self.profile_once = False
2024
2025        # The thread that can do display operations.
2026        self.thread = threading.current_thread()
2027
2028        # Init timing.
2029        init_time()
2030        self.mouse_event_time = get_time()
2031
2032        # The current window caption.
2033        self.window_caption = None
2034
2035        renpy.game.interface = self
2036        renpy.display.interface = self
2037
2038        # Are we in safe mode, from holding down shift at start?
2039        self.safe_mode = False
2040
2041        # Do we need a background screenshot?
2042        self.bgscreenshot_needed = False
2043
2044        # Event used to signal background screenshot taken.
2045        self.bgscreenshot_event = threading.Event()
2046
2047        # The background screenshot surface.
2048        self.bgscreenshot_surface = None
2049
2050        # Mouse move. If not None, information about the current mouse
2051        # move.
2052        self.mouse_move = None
2053
2054        # If in text editing mode, the current text editing event.
2055        self.text_editing = None
2056
2057        # The text rectangle after the current draw.
2058        self.text_rect = None
2059
2060        # The text rectangle after the previous draw.
2061        self.old_text_rect = None
2062
2063        # Are we a touchscreen?
2064        self.touch = renpy.exports.variant("touch")
2065
2066        # Should we use the touch keyboard?
2067        self.touch_keyboard = (self.touch and renpy.emscripten) or renpy.config.touch_keyboard
2068
2069        # Should we restart the interaction?
2070        self.restart_interaction = True
2071
2072        # For compatibility with older code.
2073        if renpy.config.periodic_callback:
2074            renpy.config.periodic_callbacks.append(renpy.config.periodic_callback)
2075
2076        # Has start been called?
2077        self.started = False
2078
2079        # Are we in fullscreen video mode?
2080        self.fullscreen_video = False
2081
2082        self.safe_mode = get_safe_mode()
2083        renpy.safe_mode_checked = True
2084
2085        # A scale factor used to compensate for the system DPI.
2086        self.dpi_scale = self.setup_dpi_scaling()
2087
2088        renpy.display.log.write("DPI scale factor: %f", self.dpi_scale)
2089
2090        # A time until which we should draw at maximum framerate.
2091        self.maximum_framerate_time = 0.0
2092        self.maximum_framerate(initial_maximum_framerate)
2093
2094        # True if this is the first interact.
2095        self.start_interact = True
2096
2097        # The time of each frame.
2098        self.frame_times = [ ]
2099
2100        # The duration of each frame, in seconds.
2101        self.frame_duration = 1.0 / 60.0
2102
2103        # The cursor cache.
2104        self.cursor_cache = None
2105
2106        # The old mouse.
2107        self.old_mouse = None
2108
2109        # A map from a layer to the duration of the current transition on that
2110        # layer.
2111        self.transition_delay = { }
2112
2113        # Is this the first frame?
2114        self.first_frame = True
2115
2116        try:
2117            self.setup_nvdrs()
2118        except:
2119            pass
2120
2121    def setup_nvdrs(self):
2122        from ctypes import cdll, c_char_p
2123        nvdrs = cdll.nvdrs
2124
2125        disable_thread_optimization = nvdrs.disable_thread_optimization
2126        restore_thread_optimization = nvdrs.restore_thread_optimization
2127        get_nvdrs_error = nvdrs.get_nvdrs_error
2128        get_nvdrs_error.restype = c_char_p
2129
2130        renpy.display.log.write("nvdrs: Loaded, about to disable thread optimizations.")
2131
2132        disable_thread_optimization()
2133        error = get_nvdrs_error()
2134        if error:
2135            renpy.display.log.write("nvdrs: %r (can be ignored)", error)
2136        else:
2137            renpy.display.log.write("nvdrs: Disabled thread optimizations.")
2138
2139        atexit.register(restore_thread_optimization)
2140
2141    def setup_dpi_scaling(self):
2142
2143        if "RENPY_HIGHDPI" in os.environ:
2144            return float(os.environ["RENPY_HIGHDPI"])
2145
2146        if not renpy.windows:
2147            return 1.0
2148
2149        try:
2150            import ctypes
2151            from ctypes import c_void_p, c_int
2152
2153            ctypes.windll.user32.SetProcessDPIAware()
2154
2155            GetDC = ctypes.windll.user32.GetDC
2156            GetDC.restype = c_void_p
2157            GetDC.argtypes = [ c_void_p ]
2158
2159            ReleaseDC = ctypes.windll.user32.ReleaseDC
2160            ReleaseDC.argtypes = [ c_void_p, c_void_p ]
2161
2162            GetDeviceCaps = ctypes.windll.gdi32.GetDeviceCaps
2163            GetDeviceCaps.restype = c_int
2164            GetDeviceCaps.argtypes = [ c_void_p, c_int ]
2165
2166            LOGPIXELSX = 88
2167
2168            dc = GetDC(None)
2169            rv = GetDeviceCaps(dc, LOGPIXELSX) / 96.0
2170            ReleaseDC(None, dc)
2171
2172            if rv < renpy.config.de_minimus_dpi_scale:
2173                renpy.display.log.write("De minimus DPI scale, was %r", rv)
2174                rv = 1.0
2175
2176            return rv
2177
2178        except:
2179            renpy.display.log.write("Could not determine DPI scale factor:")
2180            renpy.display.log.exception()
2181            return 1.0
2182
2183    def start(self):
2184        """
2185        Starts the interface, by opening a window and setting the mode.
2186        """
2187
2188        import traceback
2189
2190        if self.started:
2191            return
2192
2193        # Avoid starting on Android if we don't have focus.
2194        if renpy.android:
2195            self.check_android_start()
2196
2197        # Initialize audio.
2198        pygame.display.hint("SDL_AUDIO_DEVICE_APP_NAME", (renpy.config.name or "Ren'Py Game").encode("utf-8"))
2199
2200        renpy.audio.audio.init()
2201
2202        # Initialize pygame.
2203        try:
2204            pygame.display.init()
2205            pygame.mouse.init()
2206        except:
2207            pass
2208
2209        self.post_init()
2210
2211        renpy.display.emulator.init_emulator()
2212
2213        gc.collect()
2214
2215        if gc.garbage:
2216            del gc.garbage[:]
2217
2218        renpy.display.render.render_ready()
2219
2220        # Kill off the presplash.
2221        renpy.display.presplash.end()
2222
2223        renpy.main.log_clock("Interface start")
2224
2225        self.started = True
2226
2227        self.set_mode()
2228
2229        # Load the image fonts.
2230        renpy.text.font.load_fonts()
2231
2232        # Setup periodic event.
2233        pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
2234
2235        # Don't grab the screen.
2236        pygame.event.set_grab(False)
2237
2238        if not self.safe_mode:
2239            renpy.display.controller.init()
2240
2241        pygame.event.get([ pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP ])
2242
2243        # Create a cache of the the mouse information.
2244        if renpy.config.mouse:
2245
2246            self.cursor_cache = { }
2247
2248            cursors = { }
2249
2250            for key, cursor_list in renpy.config.mouse.items():
2251                l = [ ]
2252
2253                for i in cursor_list:
2254
2255                    if i not in cursors:
2256                        fn, x, y = i
2257                        surf = renpy.display.im.load_surface(fn)
2258                        cursors[i] = pygame.mouse.ColorCursor(surf, x, y)
2259
2260                    l.append(cursors[i])
2261
2262                self.cursor_cache[key] = l
2263
2264        s = "Total time until interface ready: {}s".format(time.time() - import_time)
2265
2266        if renpy.android and not renpy.config.log_to_stdout:
2267            print(s)
2268
2269    def post_init(self):
2270        """
2271        This is called after display init, but before the window is created.
2272        """
2273
2274        pygame.display.hint("SDL_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR", "0")
2275        pygame.display.hint("SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS", "0")
2276        pygame.display.hint("SDL_TOUCH_MOUSE_EVENTS", "1")
2277        pygame.display.hint("SDL_MOUSE_TOUCH_EVENTS", "0")
2278        pygame.display.hint("SDL_EMSCRIPTEN_ASYNCIFY", "0")
2279
2280        if renpy.config.mouse_focus_clickthrough:
2281            pygame.display.hint("SDL_MOUSE_FOCUS_CLICKTHROUGH", "1")
2282
2283        pygame.display.set_screensaver(renpy.config.allow_screensaver)
2284
2285        # Needed for Ubuntu Unity.
2286        wmclass = renpy.config.save_directory or os.path.basename(sys.argv[0])
2287        os.environ['SDL_VIDEO_X11_WMCLASS'] = wmclass
2288
2289        self.set_window_caption(force=True)
2290        self.set_icon()
2291
2292        if renpy.android:
2293            android.wakelock(True)
2294
2295        # Block events we don't use.
2296        for i in pygame.event.get_standard_events():
2297
2298            if i in enabled_events:
2299                continue
2300
2301            if i in renpy.config.pygame_events:
2302                continue
2303
2304            pygame.event.set_blocked(i)
2305
2306    def after_first_frame(self):
2307        """
2308        Called after the first frame has been drawn.
2309        """
2310
2311        if renpy.android:
2312            from jnius import autoclass
2313            PythonSDLActivity = autoclass("org.renpy.android.PythonSDLActivity")
2314            PythonSDLActivity.hidePresplash()
2315
2316            print("Hid presplash.")
2317
2318    def set_icon(self):
2319        """
2320        This is called to set up the window icon.
2321        """
2322
2323        # Window icon.
2324        icon = renpy.config.window_icon
2325
2326        if icon:
2327
2328            try:
2329                with renpy.loader.load(icon) as f:
2330                    im = renpy.display.scale.image_load_unscaled(
2331                        f,
2332                        icon,
2333                        )
2334
2335                # Convert the aspect ratio to be square.
2336                iw, ih = im.get_size()
2337                imax = max(iw, ih)
2338                square_im = renpy.display.pgrender.surface_unscaled((imax, imax), True)
2339                square_im.blit(im, ((imax - iw) // 2, (imax - ih) // 2))
2340                im = square_im
2341
2342                pygame.display.set_icon(im)
2343            except renpy.webloader.DownloadNeeded:
2344                pass
2345
2346    def set_window_caption(self, force=False):
2347
2348        window_title = renpy.config.window_title
2349
2350        if window_title is None:
2351            window_title = "A Ren'Py Game"
2352
2353        caption = renpy.translation.translate_string(window_title) + renpy.store._window_subtitle
2354
2355        if renpy.exports.get_autoreload():
2356            caption += " - autoreload"
2357
2358        if not force and caption == self.window_caption:
2359            return
2360
2361        self.window_caption = caption
2362        pygame.display.set_caption(caption.encode("utf-8"))
2363
2364    def iconify(self):
2365        pygame.display.iconify()
2366
2367    def get_draw_constructors(self):
2368        """
2369        Figures out the list of draw constructors to try.
2370        """
2371
2372        if "RENPY_RENDERER" in os.environ:
2373            renpy.config.gl2 = False
2374
2375        renderer = renpy.game.preferences.renderer
2376        renderer = os.environ.get("RENPY_RENDERER", renderer)
2377        renderer = renpy.session.get("renderer", renderer)
2378
2379        renpy.config.renderer = renderer
2380
2381        if renpy.android or renpy.ios or renpy.emscripten:
2382            renderers = [ "gles" ]
2383        elif renpy.windows:
2384            renderers = [ "gl", "angle", "gles" ]
2385        else:
2386            renderers = [ "gl", "gles" ]
2387
2388        gl2_renderers = [ ]
2389
2390        for i in [ "gl", "angle", "gles" ]:
2391
2392            if i in renderers:
2393                gl2_renderers.append(i + "2")
2394
2395        if renpy.config.gl2:
2396            renderers = gl2_renderers + renderers
2397
2398            # Prevent a performance warning if the renderer
2399            # is taken from old persistent data
2400            if renderer not in gl2_renderers:
2401                renderer = "auto"
2402
2403        else:
2404            renderers = renderers + gl2_renderers
2405
2406        if renderer in renderers:
2407            renderers = [ renderer, "sw" ]
2408
2409        if renderer == "sw":
2410            renderers = [ "sw" ]
2411
2412        # Software renderer is the last hope for PC and mac.
2413        if not (renpy.android or renpy.ios or renpy.emscripten):
2414            renderers = renderers + [ "sw" ]
2415
2416        if self.safe_mode:
2417            renderers = [ "sw" ]
2418
2419        draw_objects = { }
2420
2421        def make_draw(name, mod, cls, *args):
2422
2423            if name not in renderers:
2424                return False
2425
2426            try:
2427                __import__(mod)
2428                module = sys.modules[mod]
2429                draw_class = getattr(module, cls)
2430                draw_objects[name] = draw_class(*args)
2431                return True
2432
2433            except:
2434                renpy.display.log.write("Couldn't import {0} renderer:".format(name))
2435                renpy.display.log.exception()
2436
2437                return False
2438
2439        make_draw("gl", "renpy.gl.gldraw", "GLDraw", "gl")
2440        make_draw("angle", "renpy.gl.gldraw", "GLDraw", "angle")
2441        make_draw("gles", "renpy.gl.gldraw", "GLDraw", "gles")
2442
2443        make_draw("gl2", "renpy.gl2.gl2draw", "GL2Draw", "gl2")
2444        make_draw("angle2", "renpy.gl2.gl2draw", "GL2Draw", "angle2")
2445        make_draw("gles2", "renpy.gl2.gl2draw", "GL2Draw", "gles2")
2446
2447        make_draw("sw", "renpy.display.swdraw", "SWDraw")
2448
2449        rv = [ ]
2450
2451        def append_draw(name):
2452            if name in draw_objects:
2453                rv.append((name, draw_objects[name]))
2454            else:
2455                renpy.display.log.write("Unknown renderer: {0}".format(name))
2456
2457        for i in renderers:
2458            append_draw(i)
2459
2460        return rv
2461
2462    def kill_textures(self):
2463        """
2464        Kills all textures that have been loaded.
2465        """
2466
2467        if renpy.display.draw is not None:
2468            renpy.display.draw.kill_textures()
2469
2470        renpy.display.im.cache.clear()
2471        renpy.display.render.free_memory()
2472        renpy.text.text.layout_cache_clear()
2473        renpy.display.video.texture.clear()
2474
2475    def kill_surfaces(self):
2476        """
2477        Kills all surfaces that have been loaded.
2478        """
2479
2480        renpy.display.im.cache.clear()
2481        renpy.display.module.bo_cache = None
2482
2483    def before_resize(self):
2484        """
2485        This is called when the window has been resized.
2486        """
2487
2488        self.kill_textures()
2489
2490        # Stop the resizing.
2491        pygame.key.stop_text_input() # @UndefinedVariable
2492        pygame.key.set_text_input_rect(None) # @UndefinedVariable
2493        self.text_rect = None
2494        self.old_text_rect = None
2495        self.display_reset = False
2496
2497        self.force_redraw = True
2498
2499        # Assume we have focus until told otherwise.
2500        self.mouse_focused = True
2501        self.keyboard_focused = True
2502
2503        # Assume we're not minimized.
2504        self.minimized = False
2505
2506        # Force an interaction restart.
2507        self.restart_interaction = True
2508
2509        # True if we're doing a one-time profile.
2510        self.profile_once = False
2511
2512        # Clear the frame times.
2513        self.frame_times = [ ]
2514
2515    def set_mode(self):
2516        """
2517        This constructs the draw object and sets the initial size of the
2518        window.
2519        """
2520
2521        if renpy.session.get("_keep_renderer", False):
2522            renpy.display.render.models = renpy.display.draw.info.get("models", False)
2523            return
2524
2525        virtual_size = (renpy.config.screen_width, renpy.config.screen_height)
2526
2527        if renpy.display.draw:
2528            draws = [ renpy.display.draw ]
2529        else:
2530            draws = self.get_draw_constructors()
2531
2532        for name, draw in draws:
2533            renpy.display.log.write("")
2534            renpy.display.log.write("Initializing {0} renderer:".format(name))
2535            if draw.init(virtual_size):
2536                renpy.display.draw = draw
2537                renpy.display.render.models = draw.info.get("models", False)
2538                break
2539            else:
2540                pygame.display.destroy()
2541
2542        else:
2543            # Ensure we don't get stuck in fullscreen.
2544            renpy.game.preferences.fullscreen = False
2545            raise Exception("Could not set video mode.")
2546
2547        renpy.session["renderer"] = draw.info["renderer"]
2548        renpy.game.persistent._gl2 = renpy.config.gl2
2549
2550        if renpy.android:
2551            android.init()
2552            pygame.event.get()
2553
2554    def draw_screen(self, root_widget, fullscreen_video, draw):
2555
2556        try:
2557            renpy.display.render.per_frame = True
2558            renpy.display.screen.per_frame()
2559        finally:
2560            renpy.display.render.per_frame = False
2561
2562        surftree = renpy.display.render.render_screen(
2563            root_widget,
2564            renpy.config.screen_width,
2565            renpy.config.screen_height,
2566            )
2567
2568        if draw:
2569            renpy.display.draw.draw_screen(surftree)
2570
2571        if renpy.emscripten:
2572            emscripten.sleep(0)
2573
2574        now = time.time()
2575
2576        self.frame_times.append(now)
2577
2578        while (now - self.frame_times[0]) > renpy.config.performance_window:
2579            self.frame_times.pop(0)
2580
2581        renpy.display.render.mark_sweep()
2582        renpy.display.focus.take_focuses()
2583
2584        self.surftree = surftree
2585        self.fullscreen_video = fullscreen_video
2586
2587        if self.first_frame:
2588            self.after_first_frame()
2589            self.first_frame = False
2590
2591    def take_screenshot(self, scale, background=False):
2592        """
2593        This takes a screenshot of the current screen, and stores it so
2594        that it can gotten using get_screenshot()
2595
2596        `background`
2597           If true, we're in a background thread. So queue the request
2598           until it can be handled by the main thread.
2599        """
2600
2601        self.clear_screenshot = False
2602
2603        # Do nothing before the first interaction.
2604        if not self.started:
2605            return
2606
2607        if background and not renpy.emscripten:
2608            self.bgscreenshot_event.clear()
2609            self.bgscreenshot_needed = True
2610
2611            if not self.bgscreenshot_event.wait(1.0):
2612                raise Exception("Screenshot timed out.")
2613
2614            surf = self.bgscreenshot_surface
2615            self.bgscreenshot_surface = None
2616
2617        else:
2618
2619            surf = renpy.display.draw.screenshot(self.surftree)
2620
2621        surf = renpy.display.scale.smoothscale(surf, scale)
2622
2623        renpy.display.render.mutated_surface(surf)
2624
2625        self.screenshot_surface = surf
2626
2627        with io.BytesIO() as sio:
2628            renpy.display.module.save_png(surf, sio, 0)
2629            self.screenshot = sio.getvalue()
2630
2631    def check_background_screenshot(self):
2632        """
2633        Handles requests for a background screenshot.
2634        """
2635
2636        if self.bgscreenshot_needed:
2637            self.bgscreenshot_needed = False
2638            self.bgscreenshot_surface = renpy.display.draw.screenshot(self.surftree)
2639            self.bgscreenshot_event.set()
2640
2641    def get_screenshot(self):
2642        """
2643        Gets the current screenshot, as a string. Returns None if there isn't
2644        a current screenshot.
2645        """
2646
2647        if not self.started:
2648            return None
2649
2650        rv = self.screenshot
2651
2652        if not rv:
2653            self.take_screenshot(
2654                (renpy.config.thumbnail_width, renpy.config.thumbnail_height),
2655                background=(threading.current_thread() is not self.thread),
2656                )
2657            rv = self.screenshot
2658            self.lose_screenshot()
2659
2660        return rv
2661
2662    def lose_screenshot(self):
2663        """
2664        This deallocates the saved screenshot.
2665        """
2666
2667        self.screenshot = None
2668        self.screenshot_surface = None
2669
2670    def save_screenshot(self, filename):
2671        """
2672        Saves a full-size screenshot in the given filename.
2673        """
2674
2675        window = renpy.display.draw.screenshot(self.surftree)
2676
2677        if renpy.config.screenshot_crop:
2678            window = window.subsurface(renpy.config.screenshot_crop)
2679
2680        try:
2681            renpy.display.scale.image_save_unscaled(window, filename)
2682            if renpy.emscripten:
2683                emscripten.run_script(r'''FSDownload('%s');''' % filename)
2684            return True
2685        except:
2686            if renpy.config.debug:
2687                raise
2688
2689            return False
2690
2691    def screenshot_to_bytes(self, size=None):
2692        """
2693        This takes a screenshot of the last thing drawn, and returns it.
2694        """
2695
2696        self.clear_screenshot = False
2697
2698        # Do nothing before the first interaction.
2699        if not self.started:
2700            return
2701
2702        surf = renpy.display.draw.screenshot(self.surftree)
2703
2704        if size is not None:
2705            surf = renpy.display.scale.smoothscale(surf, size)
2706
2707        renpy.display.render.mutated_surface(surf)
2708
2709        self.screenshot_surface = surf
2710
2711        with io.BytesIO() as sio:
2712            renpy.display.module.save_png(surf, sio, 0)
2713            return sio.getvalue()
2714
2715    def show_window(self):
2716
2717        if not renpy.store._window:
2718            return
2719
2720        if not renpy.game.preferences.show_empty_window:
2721            return
2722
2723        if renpy.game.context().scene_lists.shown_window:
2724            return
2725
2726        if renpy.config.empty_window:
2727
2728            old_history = renpy.store._history # @UndefinedVariable
2729            renpy.store._history = False
2730
2731            PPP("empty window")
2732
2733            try:
2734
2735                old_say_attributes = renpy.game.context().say_attributes
2736                renpy.game.context().say_attributes = None
2737
2738                old_temporary_attributes = renpy.game.context().temporary_attributes
2739                renpy.game.context().temporary_attributes = None
2740
2741                renpy.config.empty_window()
2742
2743            finally:
2744                renpy.store._history = old_history
2745
2746                renpy.game.context().say_attributes = old_say_attributes
2747                renpy.game.context().temporary_attributes = old_temporary_attributes
2748
2749    def do_with(self, trans, paired, clear=False):
2750
2751        if renpy.config.with_callback:
2752            trans = renpy.config.with_callback(trans, paired)
2753
2754        if (not trans) or self.suppress_transition:
2755            self.with_none()
2756            return False
2757        else:
2758            self.set_transition(trans)
2759            return self.interact(trans_pause=True,
2760                                 suppress_overlay=not renpy.config.overlay_during_with,
2761                                 mouse='with',
2762                                 clear=clear)
2763
2764    def with_none(self, overlay=True):
2765        """
2766        Implements the with None command, which sets the scene we will
2767        be transitioning from.
2768        """
2769
2770        PPP("start of with none")
2771
2772        # Show the window, if that's necessary.
2773        self.show_window()
2774
2775        # Compute the overlay.
2776        if overlay:
2777            self.compute_overlay()
2778
2779        scene_lists = renpy.game.context().scene_lists
2780
2781        # Compute the scene.
2782        for layer, d in self.compute_scene(scene_lists).items():
2783            if layer is None:
2784                if not self.transition:
2785                    self.old_scene[layer] = d
2786            elif layer not in self.transition:
2787                self.old_scene[layer] = d
2788
2789        # Get rid of transient things.
2790        for i in renpy.config.overlay_layers:
2791            scene_lists.clear(i)
2792
2793        scene_lists.replace_transient()
2794        scene_lists.shown_window = False
2795
2796        if renpy.store._side_image_attributes_reset:
2797            renpy.store._side_image_attributes = None
2798            renpy.store._side_image_attributes_reset = False
2799
2800    def end_transitions(self):
2801        """
2802        This runs at the end of each interaction to remove the transitions
2803        that have run their course.
2804        """
2805
2806        layers = list(self.ongoing_transition)
2807
2808        for l in layers:
2809            if l is None:
2810                self.ongoing_transition.pop(None, None)
2811                self.transition_time.pop(None, None)
2812                self.transition_from.pop(None, None)
2813                continue
2814
2815            start = self.transition_time.get(l, self.frame_time) or 0
2816            delay = self.transition_delay.get(l, 0)
2817
2818            if (self.frame_time - start) >= delay:
2819                self.ongoing_transition.pop(l, None)
2820                self.transition_time.pop(l, None)
2821                self.transition_from.pop(l, None)
2822
2823    def set_transition(self, transition, layer=None, force=False):
2824        """
2825        Sets the transition that will be performed as part of the next
2826        interaction.
2827        """
2828
2829        if self.suppress_transition and not force:
2830            return
2831
2832        if transition is None:
2833            self.transition.pop(layer, None)
2834        else:
2835            self.transition[layer] = transition
2836
2837    def event_peek(self):
2838        """
2839        This peeks the next event. It returns None if no event exists.
2840        """
2841
2842        if renpy.emscripten:
2843            emscripten.sleep(0)
2844
2845        if self.pushed_event:
2846            return self.pushed_event
2847
2848        ev = pygame.event.poll()
2849
2850        if ev.type == pygame.NOEVENT:
2851            self.check_background_screenshot()
2852            # Seems to prevent the CPU from speeding up.
2853            renpy.display.draw.event_peek_sleep()
2854            return None
2855
2856        self.pushed_event = ev
2857
2858        return ev
2859
2860    def event_poll(self):
2861        """
2862        Called to busy-wait for an event while we're waiting to
2863        redraw a frame.
2864        """
2865
2866        if renpy.emscripten:
2867            emscripten.sleep(0)
2868
2869        if self.pushed_event:
2870            rv = self.pushed_event
2871            self.pushed_event = None
2872        else:
2873            rv = pygame.event.poll()
2874
2875        self.last_event = rv
2876
2877        return rv
2878
2879    def event_wait(self):
2880        """
2881        This is in its own function so that we can track in the
2882        profiler how much time is spent in interact.
2883        """
2884
2885        if self.pushed_event:
2886            rv = self.pushed_event
2887            self.pushed_event = None
2888            self.last_event = rv
2889            return rv
2890
2891        self.check_background_screenshot()
2892
2893        if renpy.emscripten:
2894
2895            emscripten.sleep(0)
2896
2897            while True:
2898                ev = pygame.event.poll()
2899                if ev.type != pygame.NOEVENT:
2900                    break
2901
2902                emscripten.sleep(1)
2903
2904        else:
2905            ev = pygame.event.wait()
2906
2907        self.last_event = ev
2908
2909        return ev
2910
2911    def compute_overlay(self):
2912
2913        if renpy.store.suppress_overlay:
2914            return
2915
2916        # Figure out what the overlay layer should look like.
2917        renpy.ui.layer("overlay")
2918
2919        for i in renpy.config.overlay_functions:
2920            i()
2921
2922        if renpy.game.context().scene_lists.shown_window:
2923            for i in renpy.config.window_overlay_functions:
2924                i()
2925
2926        renpy.ui.close()
2927
2928    def compute_scene(self, scene_lists):
2929        """
2930        This converts scene lists into a dictionary mapping layer
2931        name to a Fixed containing that layer.
2932        """
2933
2934        raw = { }
2935        rv = { }
2936
2937        for layer in renpy.config.layers + renpy.config.top_layers:
2938            raw[layer] = d = scene_lists.make_layer(layer, self.layer_properties[layer])
2939            rv[layer] = scene_lists.transform_layer(layer, d)
2940
2941        root = renpy.display.layout.MultiBox(layout='fixed')
2942        root.layers = { }
2943        root.raw_layers = { }
2944
2945        for layer in renpy.config.layers:
2946            root.layers[layer] = rv[layer]
2947            root.raw_layers[layer] = raw[layer]
2948            root.add(rv[layer])
2949
2950        rv[None] = root
2951
2952        return rv
2953
2954    def quit_event(self):
2955        """
2956        This is called to handle the user invoking a quit.
2957        """
2958
2959        if self.screenshot is None:
2960            renpy.exports.take_screenshot()
2961
2962        if self.quit_time > (time.time() - .75):
2963            renpy.exports.quit(save=True)
2964
2965        if self.in_quit_event:
2966            renpy.exports.quit(save=True)
2967
2968        if renpy.config.quit_action is not None:
2969            self.quit_time = time.time()
2970
2971            # Make the screen more suitable for interactions.
2972            renpy.exports.movie_stop(only_fullscreen=True)
2973            renpy.store.mouse_visible = True
2974
2975            try:
2976                self.in_quit_event = True
2977                renpy.display.behavior.run(renpy.config.quit_action)
2978            finally:
2979                self.in_quit_event = False
2980
2981        else:
2982            renpy.exports.quit(save=True)
2983
2984    def set_mouse(self, cursor):
2985        """
2986        Sets the current mouse cursor.
2987
2988        True sets a visible system cursor. False hides the cursor. A ColorCursor
2989        object sets a cursor image.
2990        """
2991
2992        if cursor is self.old_mouse:
2993            return
2994
2995        self.old_mouse = cursor
2996
2997        if cursor is True:
2998            pygame.mouse.reset()
2999            pygame.mouse.set_visible(True)
3000        elif cursor is False:
3001            pygame.mouse.reset()
3002            pygame.mouse.set_visible(False)
3003        else:
3004            pygame.mouse.set_visible(True)
3005            cursor.activate()
3006
3007    def hide_mouse(self):
3008        """
3009        Called from the controller to hide the mouse when a controller
3010        event happens.
3011        """
3012
3013        self.mouse_event_time = 0
3014
3015    def is_mouse_visible(self):
3016        # Figure out if the mouse visibility algorithm is hiding the mouse.
3017        if (renpy.config.mouse_hide_time is not None) and (self.mouse_event_time + renpy.config.mouse_hide_time < renpy.display.core.get_time()):
3018            visible = False
3019        else:
3020            visible = renpy.store.mouse_visible and (not renpy.game.less_mouse)
3021
3022        visible = visible and self.show_mouse and not (renpy.display.video.fullscreen)
3023
3024        return visible
3025
3026    def get_mouse_name(self, cache_only=False, interaction=True):
3027
3028        mouse_kind = renpy.display.focus.get_mouse()
3029
3030        if interaction and (mouse_kind is None):
3031            mouse_kind = self.mouse
3032
3033        if cache_only and (mouse_kind not in self.cursor_cache):
3034            mouse_kind = 'default'
3035
3036        if mouse_kind == 'default':
3037            mouse_kind = getattr(renpy.store, 'default_mouse', 'default')
3038
3039        return mouse_kind
3040
3041    def update_mouse(self, mouse_displayable):
3042
3043        visible = self.is_mouse_visible()
3044
3045        if mouse_displayable is not None:
3046            x, y = renpy.exports.get_mouse_pos()
3047
3048            if (0 <= x < renpy.config.screen_width) and (0 <= y < renpy.config.screen_height):
3049                visible = False
3050
3051        # If not visible, hide the mouse.
3052        if not visible:
3053            self.set_mouse(False)
3054            return
3055
3056        # Deal with a hardware mouse, the easy way.
3057        if self.cursor_cache is None:
3058            self.set_mouse(True)
3059            return
3060
3061        # Use hardware mouse if the preferences force.
3062        if renpy.game.preferences.system_cursor:
3063            if isinstance(self.old_mouse, pygame.mouse.ColorCursor):
3064                pygame.mouse.reset()
3065            self.set_mouse(True)
3066            return
3067
3068        mouse_kind = self.get_mouse_name(True)
3069
3070        if mouse_kind in self.cursor_cache:
3071            anim = self.cursor_cache[mouse_kind]
3072            cursor = anim[self.ticks % len(anim)]
3073        else:
3074            cursor = True
3075
3076        self.set_mouse(cursor)
3077
3078    def set_mouse_pos(self, x, y, duration):
3079        """
3080        Sets the mouse position. Duration can be a number of seconds or
3081        None.
3082        """
3083
3084        self.mouse_move = MouseMove(x, y, duration)
3085        self.force_redraw = True
3086
3087    def drawn_since(self, seconds_ago):
3088        """
3089        Returns true if the screen has been drawn in the last `seconds_ago`,
3090        and false otherwise.
3091        """
3092
3093        return (get_time() - self.frame_time) <= seconds_ago
3094
3095    def mobile_save(self):
3096        """
3097        Create a mobile reload file.
3098        """
3099
3100        if renpy.config.save_on_mobile_background and (not renpy.store.main_menu):
3101            renpy.loadsave.save("_reload-1")
3102
3103        renpy.persistent.update(True)
3104        renpy.persistent.save_MP()
3105
3106    def mobile_unlink(self):
3107        """
3108        Delete an unused mobile reload file.
3109        """
3110
3111        # Since we came back to life, we can get rid of the
3112        # auto-reload.
3113        renpy.loadsave.unlink_save("_reload-1")
3114
3115    def check_android_start(self):
3116        """
3117        Delays until the android screen is visible, to ensure the
3118        GL context is created properly.
3119        """
3120
3121        from jnius import autoclass
3122        SDLActivity = autoclass("org.libsdl.app.SDLActivity")
3123
3124        if SDLActivity.mHasFocus:
3125            return
3126
3127        renpy.display.log.write("App not focused at interface start, shutting down early.")
3128
3129        self.mobile_save()
3130
3131        import os
3132        os._exit(1)
3133
3134    def check_suspend(self, ev):
3135        """
3136        Handles the SDL2 suspend process.
3137        """
3138
3139        if ev.type != pygame.APP_WILLENTERBACKGROUND:
3140            return False
3141
3142        # At this point, we're about to enter the background.
3143
3144        renpy.audio.audio.pause_all()
3145
3146        if renpy.android:
3147            android.wakelock(False)
3148
3149        pygame.time.set_timer(PERIODIC, 0)
3150        pygame.time.set_timer(REDRAW, 0)
3151        pygame.time.set_timer(TIMEEVENT, 0)
3152
3153        self.mobile_save()
3154
3155        if renpy.config.quit_on_mobile_background:
3156            sys.exit(0)
3157
3158        renpy.exports.free_memory()
3159
3160        print("Entered background.")
3161
3162        while True:
3163            ev = pygame.event.wait()
3164
3165            if ev.type == pygame.APP_DIDENTERFOREGROUND:
3166                break
3167
3168        print("Entering foreground.")
3169
3170        self.mobile_unlink()
3171
3172        pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
3173
3174        renpy.audio.audio.unpause_all()
3175
3176        if renpy.android:
3177            android.wakelock(True)
3178
3179        # Reset the display so we get the GL context back.
3180        self.display_reset = True
3181        self.restart_interaction = True
3182
3183        return True
3184
3185    def enter_context(self):
3186        """
3187        Called when we enter a new context.
3188        """
3189
3190        # Stop ongoing transitions.
3191        self.ongoing_transition.clear()
3192        self.transition_from.clear()
3193        self.transition_time.clear()
3194
3195    def post_time_event(self):
3196        """
3197        Posts a time_event object to the queue.
3198        """
3199
3200        try:
3201            pygame.event.post(self.time_event)
3202        except:
3203            pass
3204
3205    def after_longpress(self):
3206        """
3207        Called after a longpress, to ignore the mouse button release.
3208        """
3209
3210        self.ignore_touch = True
3211        renpy.display.focus.mouse_handler(None, -1, -1, default=False)
3212
3213    def text_event_in_queue(self):
3214        """
3215        Returns true if the next event in the queue is a text editing event.
3216        """
3217
3218        ev = self.event_peek()
3219        if ev is None:
3220            return False
3221        else:
3222            return ev.type in (pygame.TEXTINPUT, pygame.TEXTEDITING)
3223
3224    def update_text_rect(self):
3225        """
3226        Updates the text input state and text rectangle.
3227        """
3228
3229        if renpy.store._text_rect is not None: # @UndefinedVariable
3230            self.text_rect = renpy.store._text_rect # @UndefinedVariable
3231
3232        if self.text_rect is not None:
3233
3234            not_shown = pygame.key.has_screen_keyboard_support() and not pygame.key.is_screen_keyboard_shown() # @UndefinedVariable
3235            if self.touch_keyboard:
3236                not_shown = renpy.exports.get_screen('_touch_keyboard', layer='screens') is None
3237
3238            if self.old_text_rect != self.text_rect:
3239                x, y, w, h = self.text_rect
3240                x0, y0 = renpy.display.draw.untranslate_point(x, y)
3241                x1, y1 = renpy.display.draw.untranslate_point(x + w, y + h)
3242                rect = (x0, y0, x1 - x0, y1 - y0)
3243
3244                pygame.key.set_text_input_rect(rect) # @UndefinedVariable
3245
3246            if not self.old_text_rect or not_shown:
3247                pygame.key.start_text_input() # @UndefinedVariable
3248
3249                if self.touch_keyboard:
3250                    renpy.exports.restart_interaction() # required in mobile mode
3251                    renpy.exports.show_screen('_touch_keyboard',
3252                        _layer='screens', # not 'transient' so as to be above other screens
3253                                          # not 'overlay' as it conflicts with console
3254                        _transient=True,
3255                    )
3256
3257        else:
3258            if self.old_text_rect:
3259                pygame.key.stop_text_input() # @UndefinedVariable
3260                pygame.key.set_text_input_rect(None) # @UndefinedVariable
3261
3262                if self.touch_keyboard:
3263                    renpy.exports.hide_screen('_touch_keyboard', layer='screens')
3264
3265        self.old_text_rect = self.text_rect
3266
3267    def maximum_framerate(self, t):
3268        """
3269        Forces Ren'Py to draw the screen at the maximum framerate for `t` seconds.
3270        """
3271
3272        if t is None:
3273            self.maximum_framerate_time = 0
3274        else:
3275            self.maximum_framerate_time = max(self.maximum_framerate_time, get_time() + t)
3276
3277    def interact(self, clear=True, suppress_window=False, trans_pause=False, pause=None, **kwargs):
3278        """
3279        This handles an interaction, restarting it if necessary. All of the
3280        keyword arguments are passed off to interact_core.
3281        """
3282
3283        renpy.plog(1, "start of new interaction")
3284
3285        if not self.started:
3286            self.start()
3287
3288        if self.clear_screenshot:
3289            self.lose_screenshot()
3290
3291        self.clear_screenshot = False
3292
3293        self.trans_pause = trans_pause
3294
3295        # Cancel magic error reporting.
3296        renpy.bootstrap.report_error = None
3297
3298        context = renpy.game.context()
3299
3300        if context.interacting:
3301            raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.")
3302
3303        context.interacting = True
3304
3305        # Show a missing window.
3306        if not suppress_window:
3307            self.show_window()
3308
3309        # These things can be done once per interaction.
3310
3311        preloads = self.preloads
3312        self.preloads = [ ]
3313
3314        try:
3315            self.start_interact = True
3316
3317            for i in renpy.config.start_interact_callbacks:
3318                i()
3319
3320            repeat = True
3321
3322            pause_start = get_time()
3323
3324            while repeat:
3325                repeat, rv = self.interact_core(preloads=preloads, trans_pause=trans_pause, pause=pause, pause_start=pause_start, **kwargs)
3326                self.start_interact = False
3327
3328            return rv
3329
3330        finally:
3331
3332            context.interacting = False
3333
3334            # Clean out transient stuff at the end of an interaction.
3335            if clear:
3336                scene_lists = renpy.game.context().scene_lists
3337                scene_lists.replace_transient()
3338
3339            self.end_transitions()
3340
3341            self.restart_interaction = True
3342
3343            renpy.game.context().mark_seen()
3344            renpy.game.context().scene_lists.shown_window = False
3345
3346            if renpy.game.log is not None:
3347                renpy.game.log.did_interaction = True
3348
3349            if renpy.store._side_image_attributes_reset:
3350                renpy.store._side_image_attributes = None
3351                renpy.store._side_image_attributes_reset = False
3352
3353    def consider_gc(self):
3354        """
3355        Considers if we should peform a garbage collection.
3356        """
3357
3358        if not renpy.config.manage_gc:
3359            return
3360
3361        count = gc.get_count()
3362
3363        if count[0] >= renpy.config.idle_gc_count:
3364            renpy.plog(2, "before gc")
3365
3366            if count[2] >= renpy.config.gc_thresholds[2]:
3367                gen = 2
3368            elif count[1] >= renpy.config.gc_thresholds[1]:
3369                gen = 1
3370            else:
3371                gen = 0
3372
3373            gc.collect(gen)
3374
3375            if gc.garbage:
3376                renpy.memory.print_garbage(gen)
3377                del gc.garbage[:]
3378
3379            renpy.plog(2, "after gc")
3380
3381    def idle_frame(self, can_block, expensive):
3382        """
3383        Tasks that are run during "idle" frames.
3384        """
3385
3386        if expensive:
3387            renpy.plog(1, "start idle_frame (expensive)")
3388        else:
3389            renpy.plog(1, "start idle_frame (inexpensive)")
3390
3391        # We want this to include the GC time, so we don't predict on
3392        # frames where we GC.
3393        start = get_time()
3394
3395        step = 1
3396
3397        while True:
3398
3399            if self.event_peek():
3400                break
3401
3402            if not (can_block and expensive):
3403                if get_time() > (start + .0005):
3404                    break
3405
3406            # Step 1: Run gc.
3407            if step == 1:
3408                self.consider_gc()
3409                step += 1
3410
3411            # Step 2: Push textures to GPU.
3412            elif step == 2:
3413                renpy.display.draw.ready_one_texture()
3414                step += 1
3415
3416            # Step 3: Predict more images.
3417            elif step == 3:
3418
3419                if not self.prediction_coroutine:
3420                    step += 1
3421                    continue
3422
3423                try:
3424                    result = self.prediction_coroutine.send(expensive)
3425                except ValueError:
3426                    # Saw this happen once during a quit, giving a
3427                    # ValueError: generator already executing
3428                    result = None
3429
3430                if result is None:
3431                    self.prediction_coroutine = None
3432                    step += 1
3433
3434                elif result is False:
3435                    if not expensive:
3436                        step += 1
3437
3438            # Step 4: Preload images (on emscripten)
3439            elif step == 4:
3440
3441                if expensive and renpy.emscripten:
3442                    renpy.display.im.cache.preload_thread_pass()
3443
3444                step += 1
3445
3446            # Step 5: Autosave.
3447            elif step == 5:
3448
3449                if not self.did_autosave:
3450                    renpy.loadsave.autosave()
3451                    renpy.persistent.check_update()
3452                    self.did_autosave = True
3453
3454                step += 1
3455
3456            else:
3457                break
3458
3459        if expensive:
3460            renpy.plog(1, "end idle_frame (expensive)")
3461        else:
3462            renpy.plog(1, "end idle_frame (inexpensive)")
3463
3464    def interact_core(self,
3465                      show_mouse=True,
3466                      trans_pause=False,
3467                      suppress_overlay=False,
3468                      suppress_underlay=False,
3469                      mouse='default',
3470                      preloads=[],
3471                      roll_forward=None,
3472                      pause=False,
3473                      pause_start=0,
3474                      ):
3475        """
3476        This handles one cycle of displaying an image to the user,
3477        and then responding to user input.
3478
3479        @param show_mouse: Should the mouse be shown during this
3480        interaction? Only advisory, and usually doesn't work.
3481
3482        @param trans_pause: If given, we must have a transition. Should we
3483        add a pause behavior during the transition?
3484
3485        @param suppress_overlay: This suppresses the display of the overlay.
3486        @param suppress_underlay: This suppresses the display of the underlay.
3487
3488        `pause`
3489            If not None, the amount of time before the interaction ends with
3490            False being returned.
3491        """
3492
3493        renpy.plog(1, "start interact_core")
3494
3495        suppress_overlay = suppress_overlay or renpy.store.suppress_overlay
3496
3497        # Store the various parameters.
3498        self.suppress_overlay = suppress_overlay
3499        self.suppress_underlay = suppress_underlay
3500        self.trans_pause = trans_pause
3501
3502        # Show default screens.
3503        renpy.display.screen.show_overlay_screens(suppress_overlay)
3504
3505        # Prepare screens, if need be.
3506        renpy.display.screen.prepare_screens()
3507
3508        self.roll_forward = roll_forward
3509        self.show_mouse = show_mouse
3510
3511        suppress_transition = renpy.config.skipping or renpy.game.less_updates
3512
3513        # The global one.
3514        self.suppress_transition = False
3515
3516        # Figure out transitions.
3517        if suppress_transition:
3518            self.ongoing_transition.clear()
3519            self.transition_from.clear()
3520            self.transition_time.clear()
3521        else:
3522            for k in self.transition:
3523                if k not in self.old_scene:
3524                    continue
3525
3526                self.ongoing_transition[k] = self.transition[k]
3527                self.transition_from[k] = self.old_scene[k]._in_current_store()
3528                self.transition_time[k] = None
3529
3530        self.transition.clear()
3531
3532        # Safety condition, prevents deadlocks.
3533        if trans_pause:
3534            if not self.ongoing_transition:
3535                return False, None
3536            if None not in self.ongoing_transition:
3537                return False, None
3538            if suppress_transition:
3539                return False, None
3540            if not self.old_scene:
3541                return False, None
3542
3543        # Check to see if the language has changed.
3544        renpy.translation.check_language()
3545
3546        # We just restarted.
3547        self.restart_interaction = False
3548
3549        # Setup the mouse.
3550        self.mouse = mouse
3551
3552        # The start and end times of this interaction.
3553        start_time = get_time()
3554        end_time = start_time
3555
3556        self.frame_time = start_time
3557
3558        for i in renpy.config.interact_callbacks:
3559            i()
3560
3561        # Set the window caption.
3562        self.set_window_caption()
3563
3564        # Tick time forward.
3565        renpy.display.im.cache.tick()
3566        renpy.text.text.text_tick()
3567        renpy.display.predict.reset()
3568
3569        # Clear the size groups.
3570        renpy.display.layout.size_groups.clear()
3571
3572        # Clear the set of updated screens.
3573        renpy.display.screen.updated_screens.clear()
3574
3575        # Clear some events.
3576        pygame.event.clear((pygame.MOUSEMOTION,
3577                            PERIODIC,
3578                            TIMEEVENT,
3579                            REDRAW))
3580
3581        # Add a single TIMEEVENT to the queue.
3582        self.post_time_event()
3583
3584        # Figure out the scene list we want to show.
3585        scene_lists = renpy.game.context().scene_lists
3586
3587        # Remove the now-hidden things.
3588        scene_lists.remove_hidden()
3589
3590        # Compute the overlay.
3591        if not suppress_overlay:
3592            self.compute_overlay()
3593
3594        # The root widget of everything that is displayed on the screen.
3595        root_widget = renpy.display.layout.MultiBox(layout='fixed')
3596        root_widget.layers = { }
3597
3598        # A list of widgets that are roots of trees of widgets that are
3599        # considered for focusing.
3600        focus_roots = [ ]
3601
3602        # Add the underlay to the root widget.
3603        if not suppress_underlay:
3604            for i in renpy.config.underlay:
3605                root_widget.add(i)
3606                focus_roots.append(i)
3607
3608            if roll_forward is not None:
3609                rfw = renpy.display.behavior.RollForward(roll_forward)
3610                root_widget.add(rfw)
3611                focus_roots.append(rfw)
3612
3613        # Figure out the scene. (All of the layers, and the root.)
3614        scene = self.compute_scene(scene_lists)
3615        renpy.display.tts.set_root(scene[None])
3616
3617        renpy.plog(1, "computed scene")
3618
3619        # If necessary, load all images here.
3620        for w in scene.values():
3621            try:
3622                renpy.display.predict.displayable(w)
3623            except:
3624                pass
3625
3626        renpy.plog(1, "final predict")
3627
3628        # The root widget of all of the layers.
3629        layers_root = renpy.display.layout.MultiBox(layout='fixed')
3630        layers_root.layers = { }
3631        layers_root.raw_layers = scene[None].raw_layers
3632
3633        def add_layer(where, layer):
3634
3635            scene_layer = scene[layer]
3636            focus_roots.append(scene_layer)
3637
3638            if (self.ongoing_transition.get(layer, None) and
3639                    not suppress_transition):
3640
3641                trans = self.ongoing_transition[layer](
3642                    old_widget=self.transition_from[layer],
3643                    new_widget=scene_layer)
3644
3645                if not isinstance(trans, Displayable):
3646                    raise Exception("Expected transition to be a displayable, not a %r" % trans)
3647
3648                transition_time = self.transition_time.get(layer, None)
3649
3650                where.add(trans, transition_time, transition_time)
3651                where.layers[layer] = trans
3652
3653            else:
3654                where.layers[layer] = scene_layer
3655                where.add(scene_layer)
3656
3657        # Add layers (perhaps with transitions) to the layers root.
3658        for layer in renpy.config.layers:
3659            add_layer(layers_root, layer)
3660
3661        # Add layers_root to root_widget, perhaps through a transition.
3662        if (self.ongoing_transition.get(None, None) and
3663                not suppress_transition):
3664
3665            old_root = renpy.display.layout.MultiBox(layout='fixed')
3666            old_root.layers = { }
3667            old_root.raw_layers = self.transition_from[None].raw_layers
3668
3669            for layer in renpy.config.layers:
3670                d = self.transition_from[None].layers[layer]
3671                old_root.layers[layer] = d
3672                old_root.add(d)
3673
3674            trans = self.ongoing_transition[None](
3675                old_widget=old_root,
3676                new_widget=layers_root)
3677
3678            if not isinstance(trans, Displayable):
3679                raise Exception("Expected transition to be a displayable, not a %r" % trans)
3680
3681            transition_time = self.transition_time.get(None, None)
3682            root_widget.add(trans, transition_time, transition_time)
3683
3684            if (transition_time is None) and isinstance(trans, renpy.display.transform.Transform):
3685                trans.update_state()
3686
3687            if trans_pause:
3688
3689                if renpy.store._dismiss_pause:
3690                    sb = renpy.display.behavior.SayBehavior(dismiss=[], dismiss_unfocused='dismiss')
3691                else:
3692                    sb = renpy.display.behavior.SayBehavior(dismiss=[], dismiss_unfocused='dismiss_hard_pause')
3693
3694                root_widget.add(sb)
3695                focus_roots.append(sb)
3696
3697                pb = renpy.display.behavior.PauseBehavior(trans.delay)
3698                root_widget.add(pb, transition_time, transition_time)
3699                focus_roots.append(pb)
3700
3701        else:
3702            root_widget.add(layers_root)
3703
3704        if pause is not None:
3705            pb = renpy.display.behavior.PauseBehavior(pause)
3706            root_widget.add(pb, pause_start, pause_start)
3707            focus_roots.append(pb)
3708
3709        # Add top_layers to the root_widget.
3710        for layer in renpy.config.top_layers:
3711            add_layer(root_widget, layer)
3712
3713        for i in renpy.display.emulator.overlay:
3714            root_widget.add(i)
3715
3716        mouse_displayable = renpy.config.mouse_displayable
3717        if mouse_displayable is not None:
3718            if not isinstance(mouse_displayable, Displayable):
3719                mouse_displayable = mouse_displayable()
3720
3721            if mouse_displayable is not None:
3722                root_widget.add(mouse_displayable, 0, 0)
3723
3724        del add_layer
3725
3726        self.prediction_coroutine = renpy.display.predict.prediction_coroutine(root_widget)
3727        self.prediction_coroutine.send(None)
3728
3729        # Clean out the registered adjustments.
3730        renpy.display.behavior.adj_registered.clear()
3731
3732        # Clean up some movie-related things.
3733        renpy.display.video.early_interact()
3734
3735        # Call per-interaction code for all widgets.
3736        renpy.display.behavior.input_pre_per_interact()
3737        root_widget.visit_all(lambda i : i.per_interact())
3738        renpy.display.behavior.input_post_per_interact()
3739
3740        # Now, update various things regarding scenes and transitions,
3741        # so we are ready for a new interaction or a restart.
3742        self.old_scene = scene
3743
3744        # Okay, from here on we now have a single root widget (root_widget),
3745        # which we will try to show to the user.
3746
3747        # Figure out what should be focused.
3748        renpy.display.focus.before_interact(focus_roots)
3749
3750        # Something updated the screens. Deal with it now, so the player doesn't
3751        # see it.
3752        if self.restart_interaction:
3753            return True, None
3754
3755        # Redraw the screen.
3756        needs_redraw = True
3757
3758        # First pass through the while loop?
3759        first_pass = True
3760
3761        # We don't yet know when the interaction began.
3762        self.interact_time = None
3763
3764        # We only want to do autosave once.
3765        self.did_autosave = False
3766
3767        old_timeout_time = None
3768        old_redraw_time = None
3769
3770        rv = None
3771
3772        # Start sound.
3773        renpy.audio.audio.interact()
3774
3775        # How long until we redraw.
3776        _redraw_in = 3600
3777
3778        # Have we drawn a frame yet?
3779        video_frame_drawn = False
3780
3781        # We're no longer after rollback.
3782        renpy.game.after_rollback = False
3783
3784        # How many frames have we shown so far?
3785        frame = 0
3786
3787        can_block = False
3788
3789        # This try block is used to force cleanup even on termination
3790        # caused by an exception propagating through this function.
3791        try:
3792
3793            while rv is None:
3794
3795                renpy.plog(1, "start of interact while loop")
3796
3797                renpy.execution.not_infinite_loop(10)
3798
3799                # Check for autoreload.
3800                renpy.loader.check_autoreload()
3801
3802                if renpy.emscripten or os.environ.get('RENPY_SIMULATE_DOWNLOAD', False):
3803                    renpy.webloader.process_downloaded_resources()
3804
3805                for i in renpy.config.needs_redraw_callbacks:
3806                    if i():
3807                        needs_redraw = True
3808
3809                # Check for a fullscreen change.
3810                if renpy.game.preferences.fullscreen != self.fullscreen:
3811                    renpy.display.draw.resize()
3812
3813                # Ask if the game has changed size.
3814                if renpy.display.draw.update(force=self.display_reset):
3815                    needs_redraw = True
3816
3817                # Redraw the screen.
3818                if (self.force_redraw or
3819                    ((first_pass or not pygame.event.peek(ALL_EVENTS)) and
3820                     renpy.display.draw.should_redraw(needs_redraw, first_pass, can_block))):
3821
3822                    self.force_redraw = False
3823
3824                    renpy.display.render.process_redraws()
3825
3826                    # If we have a movie, start showing it.
3827                    fullscreen_video = renpy.display.video.interact()
3828
3829                    # Clean out the redraws, if we have to.
3830                    # renpy.display.render.kill_redraws()
3831
3832                    self.text_rect = None
3833
3834                    # Draw the screen.
3835                    self.frame_time = get_time()
3836
3837                    renpy.audio.audio.advance_time() # Sets the time of all video frames.
3838
3839                    self.draw_screen(root_widget, fullscreen_video, (not fullscreen_video) or video_frame_drawn)
3840
3841                    if first_pass:
3842                        if not self.interact_time:
3843                            self.interact_time = max(self.frame_time, get_time() - self.frame_duration)
3844
3845                        scene_lists.set_times(self.interact_time)
3846
3847                        for k, v in self.transition_time.items():
3848                            if v is None:
3849                                self.transition_time[k] = self.interact_time
3850
3851                        renpy.display.render.adjust_render_cache_times(self.frame_time, self.interact_time)
3852
3853                    frame += 1
3854                    renpy.config.frames += 1
3855
3856                    # If profiling is enabled, report the profile time.
3857                    if renpy.config.profile or self.profile_once:
3858
3859                        renpy.plog(0, "end frame")
3860                        renpy.performance.analyze()
3861                        renpy.performance.clear()
3862                        renpy.plog(0, "start frame")
3863
3864                        self.profile_once = False
3865
3866                    if first_pass and self.last_event and self.last_event.type in [ pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION ]:
3867
3868                        x, y = renpy.display.draw.get_mouse_pos()
3869                        ev, x, y = renpy.display.emulator.emulator(self.last_event, x, y)
3870
3871                        if self.ignore_touch:
3872                            x = -1
3873                            y = -1
3874
3875                        if renpy.android and self.last_event.type == pygame.MOUSEBUTTONUP:
3876                            x = -1
3877                            y = -1
3878
3879                        renpy.display.focus.mouse_handler(None, x, y, default=False)
3880
3881                    needs_redraw = False
3882                    first_pass = False
3883
3884                    pygame.time.set_timer(REDRAW, 0)
3885                    pygame.event.clear([REDRAW])
3886                    old_redraw_time = None
3887
3888                    self.update_text_rect()
3889
3890                    renpy.test.testexecution.execute()
3891
3892                # Move the mouse, if necessary.
3893                if self.mouse_move is not None:
3894                    if not self.mouse_move.perform():
3895                        self.mouse_move = None
3896
3897                # See if we want to restart the interaction entirely.
3898                if self.restart_interaction and not self.display_reset:
3899                    return True, None
3900
3901                # Determine if we need a redraw. (We want to run these
3902                # functions, so we put them first to prevent short-circuiting.)
3903
3904                if renpy.display.video.frequent():
3905                    needs_redraw = True
3906                    video_frame_drawn = True
3907
3908                if renpy.display.render.check_redraws():
3909                    needs_redraw = True
3910
3911                # How many seconds until we timeout.
3912                _timeout_in = 3600
3913
3914                # Handle the redraw timer.
3915                redraw_time = renpy.display.render.redraw_time()
3916
3917                # We only need to set the REDRAW timer if we can block.
3918                can_block = renpy.display.draw.can_block()
3919
3920                if self.maximum_framerate_time > get_time():
3921                    can_block = False
3922
3923                if (redraw_time is not None) and (not needs_redraw) and can_block:
3924                    if redraw_time != old_redraw_time:
3925                        time_left = redraw_time - get_time()
3926                        time_left = min(time_left, 3600)
3927                        _redraw_in = time_left
3928
3929                        if time_left <= 0:
3930                            try:
3931                                pygame.event.post(self.redraw_event)
3932                            except:
3933                                pass
3934                            pygame.time.set_timer(REDRAW, 0)
3935                        else:
3936                            pygame.time.set_timer(REDRAW, max(int(time_left * 1000), 1))
3937
3938                        old_redraw_time = redraw_time
3939                else:
3940                    _redraw_in = 3600
3941                    pygame.time.set_timer(REDRAW, 0)
3942
3943                # Handle the timeout timer.
3944                if not self.timeout_time:
3945                    pygame.time.set_timer(TIMEEVENT, 0)
3946                else:
3947                    time_left = self.timeout_time - get_time()
3948                    time_left = min(time_left, 3600)
3949                    _timeout_in = time_left
3950
3951                    if time_left <= 0:
3952                        self.timeout_time = None
3953                        pygame.time.set_timer(TIMEEVENT, 0)
3954                        self.post_time_event()
3955                    elif self.timeout_time != old_timeout_time:
3956                        # Always set to at least 1ms.
3957                        pygame.time.set_timer(TIMEEVENT, int(time_left * 1000 + 1))
3958                        old_timeout_time = self.timeout_time
3959
3960                if can_block or (frame >= renpy.config.idle_frame):
3961                    expensive = not (needs_redraw or (_redraw_in < .2) or (_timeout_in < .2) or renpy.display.video.playing())
3962                    self.idle_frame(can_block, expensive)
3963
3964                if needs_redraw or (not can_block) or self.mouse_move or renpy.display.video.playing():
3965                    renpy.plog(1, "pre peek")
3966                    ev = self.event_poll()
3967                    renpy.plog(1, "post peek {!r}", ev)
3968                else:
3969                    renpy.plog(1, "pre wait")
3970                    ev = self.event_wait()
3971                    renpy.plog(1, "post wait {!r}", ev)
3972
3973                if ev.type == pygame.NOEVENT:
3974
3975                    if can_block and (not needs_redraw) and (not self.prediction_coroutine) and (not self.mouse_move):
3976                        pygame.time.wait(1)
3977
3978                    continue
3979
3980                # Recognize and ignore AltGr on Windows.
3981                if ev.type == pygame.KEYDOWN:
3982                    if ev.key == pygame.K_LCTRL:
3983
3984                        ev2 = self.event_peek()
3985
3986                        if (ev2 is not None) and (ev2.type == pygame.KEYDOWN):
3987                            if ev2.key == pygame.K_RALT:
3988                                continue
3989
3990                # Check to see if the OS is asking us to suspend (on Android
3991                # and iOS.)
3992                if self.check_suspend(ev):
3993                    continue
3994
3995                # Try to merge an TIMEEVENT with other timeevents.
3996                if ev.type == TIMEEVENT:
3997                    old_timeout_time = None
3998                    pygame.event.clear([TIMEEVENT])
3999
4000                    # Set the modal flag to False.
4001                    ev.modal = False
4002
4003                # On Android, where we have multiple mouse buttons, we can
4004                # merge a mouse down and mouse up event with its successor. This
4005                # prevents us from getting overwhelmed with too many events on
4006                # a multitouch screen.
4007                if renpy.android and (ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP):
4008                    pygame.event.clear(ev.type)
4009
4010                # Handle redraw timeouts.
4011                if ev.type == REDRAW:
4012                    pygame.event.clear([REDRAW])
4013                    old_redraw_time = None
4014                    continue
4015
4016                # Handle periodic events. This includes updating the mouse timers (and through the loop,
4017                # the mouse itself), and the audio system periodic calls.
4018                if ev.type == PERIODIC:
4019                    events = 1 + len(pygame.event.get([PERIODIC]))
4020                    self.ticks += events
4021
4022                    for i in renpy.config.periodic_callbacks:
4023                        i()
4024
4025                    renpy.audio.audio.periodic()
4026                    renpy.display.tts.periodic()
4027                    renpy.display.controller.periodic()
4028
4029                    self.update_mouse(mouse_displayable)
4030
4031                    continue
4032
4033                # Handle quit specially for now.
4034                if ev.type == pygame.QUIT:
4035                    self.quit_event()
4036                    continue
4037
4038                # Ignore KEY-events while text is being edited (usually with an IME).
4039                if ev.type == pygame.TEXTEDITING:
4040                    if ev.text:
4041                        self.text_editing = ev
4042                    else:
4043                        self.text_editing = None
4044                elif ev.type == pygame.TEXTINPUT:
4045                    self.text_editing = None
4046
4047                elif ev.type == pygame.KEYMAPCHANGED:
4048
4049                    # Clear the mods when the keymap is changed, such as when
4050                    # an IME is selected. This fixes a problem on Windows 10 where
4051                    # super+space won't unset super.
4052                    pygame.key.set_mods(0)
4053                    continue
4054
4055                elif self.text_editing and ev.type in [ pygame.KEYDOWN, pygame.KEYUP ]:
4056                    continue
4057
4058                if ev.type == pygame.VIDEOEXPOSE:
4059                    # Needed to force the display to redraw after expose in
4060                    # the software renderer.
4061
4062                    if isinstance(renpy.display.draw, renpy.display.swdraw.SWDraw):
4063                        renpy.display.draw.full_redraw = True
4064                        renpy.game.interface.force_redraw = True
4065
4066                    continue
4067
4068                # Handle videoresize.
4069                if ev.type == pygame.VIDEORESIZE:
4070
4071                    renpy.game.interface.full_redraw = True
4072                    renpy.game.interface.force_redraw = True
4073
4074                    continue
4075
4076                # If we're ignoring touch events, and get a mouse up, stop
4077                # ignoring those events.
4078                if self.ignore_touch and \
4079                        ev.type == pygame.MOUSEBUTTONUP and \
4080                        ev.button == 1:
4081
4082                    self.ignore_touch = False
4083                    continue
4084
4085                # Merge mousemotion events.
4086                if ev.type == pygame.MOUSEMOTION:
4087                    evs = pygame.event.get([pygame.MOUSEMOTION])
4088                    if len(evs):
4089                        ev = evs[-1]
4090
4091                    if renpy.windows:
4092                        self.mouse_focused = True
4093
4094                # Handle mouse event time, and ignoring touch.
4095                if ev.type == pygame.MOUSEMOTION or \
4096                        ev.type == pygame.MOUSEBUTTONDOWN or \
4097                        ev.type == pygame.MOUSEBUTTONUP:
4098
4099                    self.mouse_event_time = renpy.display.core.get_time()
4100
4101                    if self.ignore_touch:
4102                        renpy.display.focus.mouse_handler(None, -1, -1, default=False)
4103
4104                    if mouse_displayable:
4105                        renpy.display.render.redraw(mouse_displayable, 0)
4106
4107                # Handle focus notifications.
4108                if ev.type == pygame.ACTIVEEVENT:
4109
4110                    if ev.state & 1:
4111                        if not ev.gain:
4112                            renpy.display.focus.clear_focus()
4113
4114                        self.mouse_focused = ev.gain
4115
4116                        if mouse_displayable:
4117                            renpy.display.render.redraw(mouse_displayable, 0)
4118
4119                    if ev.state & 2:
4120                        self.keyboard_focused = ev.gain
4121
4122                    pygame.key.set_mods(0)
4123
4124                # This returns the event location. It also updates the
4125                # mouse state as necessary.
4126                x, y = renpy.display.draw.mouse_event(ev)
4127                x, y = renpy.test.testmouse.get_mouse_pos(x, y)
4128
4129                ev, x, y = renpy.display.emulator.emulator(ev, x, y)
4130                if ev is None:
4131                    continue
4132
4133                if not self.mouse_focused or self.ignore_touch:
4134                    x = -1
4135                    y = -1
4136
4137                # This can set the event to None, to ignore it.
4138                ev = renpy.display.controller.event(ev)
4139                if not ev:
4140                    continue
4141
4142                # Handle skipping.
4143                renpy.display.behavior.skipping(ev)
4144
4145                self.event_time = end_time = get_time()
4146
4147                try:
4148
4149                    if self.touch:
4150                        renpy.display.gesture.recognizer.event(ev, x, y) # @UndefinedVariable
4151
4152                    renpy.plog(1, "start mouse focus handling")
4153
4154                    # Handle the event normally.
4155                    rv = renpy.display.focus.mouse_handler(ev, x, y)
4156
4157                    renpy.plog(1, "start event handling")
4158
4159                    if rv is None:
4160                        rv = root_widget.event(ev, x, y, 0)
4161
4162                    if rv is None:
4163                        rv = renpy.display.focus.key_handler(ev)
4164
4165                    renpy.plog(1, "finish event handling")
4166
4167                    if rv is not None:
4168                        break
4169
4170                    # Handle displayable inspector.
4171                    if renpy.config.inspector:
4172                        if renpy.display.behavior.map_event(ev, "inspector"):
4173                            l = self.surftree.main_displayables_at_point(x, y, renpy.config.transient_layers + renpy.config.context_clear_layers + renpy.config.overlay_layers)
4174                            renpy.game.invoke_in_new_context(renpy.config.inspector, l)
4175                        elif renpy.display.behavior.map_event(ev, "full_inspector"):
4176                            l = self.surftree.main_displayables_at_point(x, y, renpy.config.layers)
4177                            renpy.game.invoke_in_new_context(renpy.config.inspector, l)
4178
4179                    # Handle the dismissing of non trans_pause transitions.
4180                    if self.ongoing_transition.get(None, None) and (not suppress_transition) and (not trans_pause) and (renpy.config.dismiss_blocking_transitions):
4181
4182                        if renpy.store._dismiss_pause:
4183                            dismiss = "dismiss"
4184                        else:
4185                            dismiss = "dismiss_hard_pause"
4186
4187                        if renpy.display.behavior.map_event(ev, dismiss):
4188                            self.transition.pop(None, None)
4189                            self.ongoing_transition.pop(None, None)
4190                            self.transition_time.pop(None, None)
4191                            self.transition_from.pop(None, None)
4192                            self.restart_interaction = True
4193                            raise IgnoreEvent()
4194
4195                except IgnoreEvent:
4196                    # An ignored event can change the timeout. So we want to
4197                    # process an TIMEEVENT to ensure that the timeout is
4198                    # set correctly
4199
4200                    if ev.type != TIMEEVENT:
4201                        self.post_time_event()
4202
4203                    # On mobile, if an event originates from the touch mouse, unfocus.
4204                    if renpy.mobile and (ev.type == pygame.MOUSEBUTTONUP) and getattr(ev, "which", 0) == 4294967295:
4205                        if not self.restart_interaction:
4206                            renpy.display.focus.mouse_handler(None, -1, -1, default=False)
4207
4208                # Check again after handling the event.
4209                needs_redraw |= renpy.display.render.check_redraws()
4210
4211                if self.restart_interaction:
4212                    return True, None
4213
4214            # If we were trans-paused and rv is true, suppress
4215            # transitions up to the next interaction.
4216            if trans_pause and rv:
4217                self.suppress_transition = True
4218
4219            # But wait, there's more! The finally block runs some cleanup
4220            # after this.
4221            return False, rv
4222
4223        except EndInteraction as e:
4224            return False, e.value
4225
4226        finally:
4227
4228            # Determine the transition delay for each layer.
4229            self.transition_delay = { k : getattr(v, "delay", 0) for k, v in layers_root.layers.items() }
4230
4231            # Clean out the overlay layers.
4232            for i in renpy.config.overlay_layers:
4233                scene_lists.clear(i)
4234
4235            # Stop ongoing preloading.
4236            renpy.display.im.cache.end_tick()
4237
4238            # We no longer disable periodic between interactions.
4239            # pygame.time.set_timer(PERIODIC, 0)
4240
4241            pygame.time.set_timer(TIMEEVENT, 0)
4242            pygame.time.set_timer(REDRAW, 0)
4243
4244            self.consider_gc()
4245
4246            renpy.game.context().runtime += end_time - start_time
4247
4248            # Restart the old interaction, which also causes a
4249            # redraw if needed.
4250            self.restart_interaction = True
4251
4252            renpy.plog(1, "end interact_core")
4253
4254            # print("It took", frames, "frames.")
4255
4256    def timeout(self, offset):
4257        if offset < 0:
4258            return
4259
4260        if self.timeout_time:
4261            self.timeout_time = min(self.event_time + offset, self.timeout_time)
4262        else:
4263            self.timeout_time = self.event_time + offset
4264
4265    def finish_pending(self):
4266        """
4267        Called before a quit or restart to finish any pending work that might
4268        block other threads.
4269        """
4270
4271        self.check_background_screenshot()
4272