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