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 22from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 23from renpy.compat import * 24 25import renpy.display 26import time 27import collections 28 29import datetime 30 31# Profiling #################################################################### 32 33profile_log = renpy.log.open("profile_screen", developer=True, append=False, flush=False) 34 35# A map from screen name to ScreenProfile object. 36profile = { } 37 38 39class ScreenProfile(renpy.object.Object): 40 """ 41 :doc: profile_screen 42 :name: renpy.profile_screen 43 44 """ 45 46 def __init__(self, name, predict=False, show=False, update=False, request=False, time=False, debug=False, const=False): 47 """ 48 Requests screen profiling for the screen named `name`, which 49 must be a string. 50 51 Apart from `name`, all arguments must be supplied as keyword 52 arguments. This function takes three groups of arguments. 53 54 55 The first group of arguments determines when profiling occurs. 56 57 `predict` 58 If true, profiling occurs when the screen is being predicted. 59 60 `show` 61 If true, profiling occurs when the screen is first shown. 62 63 `update` 64 If true, profiling occurs when the screen is updated. 65 66 `request` 67 If true, profiling occurs when requested by pressing F8. 68 69 The second group of arguments controls what profiling output is 70 produced when profiling occurs. 71 72 `time` 73 If true, Ren'Py will log the amount of time it takes to evaluate 74 the screen. 75 76 `debug` 77 If true, Ren'Py will log information as to how screens are 78 evaluated, including: 79 80 * Which displayables Ren'Py considers constant. 81 * Which arguments, if any, needed to be evaluated. 82 * Which displayables were reused. 83 84 Producing and saving this debug information takes a noticeable 85 amount of time, and so the `time` output should not be considered 86 reliable if `debug` is set. 87 88 The last group of arguments controls what output is produced once 89 per Ren'Py run. 90 91 `const` 92 Displays the variables in the screen that are marked as const and 93 not-const. 94 95 All profiling output will be logged to profile_screen.txt in the game 96 directory. 97 """ 98 99 self.predict = predict 100 self.show = show 101 self.update = update 102 self.request = request 103 104 self.time = time 105 self.debug = debug 106 107 self.const = const 108 109 if name is not None: 110 if isinstance(name, basestring): 111 name = tuple(name.split()) 112 profile[name] = self 113 114 115def get_profile(name): 116 """ 117 Returns the profile object for the screen with `name`, or a default 118 profile object if none exists. 119 120 `name` 121 A string or tuple. 122 """ 123 124 if isinstance(name, basestring): 125 name = tuple(name.split()) 126 127 if name in profile: 128 return profile[name] 129 else: 130 return ScreenProfile(None) 131 132# Cache ######################################################################## 133 134 135# A map from screen name to a list of ScreenCache objects. We ensure the cache 136# does not exceed config.screen_cache_size for each screen. 137predict_cache = collections.defaultdict(list) 138 139 140class ScreenCache(object): 141 """ 142 Represents an entry in the screen cache. Upon creation, puts itself into 143 the screen cache. 144 """ 145 146 def __init__(self, screen, args, kwargs, cache): 147 148 if screen.ast is None: 149 return 150 151 self.args = args 152 self.kwargs = kwargs 153 self.cache = cache 154 155 pc = predict_cache[screen] 156 157 pc.append(self) 158 159 if len(pc) > renpy.config.screen_cache_size: 160 pc.pop(0) 161 162 163cache_put = ScreenCache 164 165 166def cache_get(screen, args, kwargs): 167 """ 168 Returns the cache to use when `screen` is accessed with `args` and 169 `kwargs`. 170 """ 171 172 if screen.ast is None: 173 return { } 174 175 pc = predict_cache[screen] 176 177 if not pc: 178 return { } 179 180 for sc in pc: 181 182 # Reuse w/ same arguments. 183 if sc.args == args and sc.kwargs == kwargs: 184 pc.remove(sc) 185 break 186 else: 187 188 # Reuse the oldest. 189 sc = pc.pop(0) 190 191 return sc.cache 192 193# Screens ##################################################################### 194 195 196class Screen(renpy.object.Object): 197 """ 198 A screen is a collection of widgets that are displayed together. 199 This class stores information about the screen. 200 """ 201 202 sensitive = "True" 203 204 def __init__(self, 205 name, 206 function, 207 modal="False", 208 zorder="0", 209 tag=None, 210 predict=None, 211 variant=None, 212 parameters=False, 213 location=None, 214 layer="screens", 215 sensitive="True"): 216 217 # The name of this screen. 218 if isinstance(name, basestring): 219 name = tuple(name.split()) 220 221 self.name = name 222 223 if (variant is None) or isinstance(variant, basestring): 224 variant = [ variant ] 225 226 for v in variant: 227 screens[name[0], v] = self 228 screens_by_name[name[0]][v] = self 229 230 # A function that can be called to display the screen. 231 self.function = function 232 233 # If this is a SL2 screen, the SLScreen node at the root of this 234 # screen. 235 if isinstance(function, renpy.sl2.slast.SLScreen): # @UndefinedVariable 236 self.ast = function 237 else: 238 self.ast = None 239 240 # Expression: Are we modal? (A modal screen ignores screens under it.) 241 self.modal = modal 242 243 # Expression: Our zorder. 244 self.zorder = zorder 245 246 # The tag associated with the screen. 247 self.tag = tag or name[0] 248 249 # Can this screen be predicted? 250 if predict is None: 251 predict = renpy.config.predict_screens 252 253 self.predict = predict 254 255 # True if this screen takes parameters via _args and _kwargs. 256 self.parameters = parameters 257 258 # The location (filename, linenumber) of this screen. 259 self.location = location 260 261 # The layer the screen will be shown on. 262 self.layer = layer 263 264 # Is this screen sensitive? An expression. 265 self.sensitive = sensitive 266 267 global prepared 268 global analyzed 269 270 prepared = False 271 analyzed = False 272 273 274# Phases we can be in. 275PREDICT = 0 # Predicting the screen before it is shown. 276SHOW = 1 # Showing the screen for the first time. 277UPDATE = 2 # Showing the screen for the second and later times. 278HIDE = 3 # After the screen has been hid with "hide screen" (or the end of call screen). 279OLD = 4 # A copy of the screen in the old side of a transition. 280 281phase_name = [ 282 "PREDICT", 283 "SHOW", 284 "UPDATE", 285 "HIDE", 286 "OLD", 287 ] 288 289 290class ScreenDisplayable(renpy.display.layout.Container): 291 """ 292 A screen is a collection of widgets that are displayed together. This 293 class is responsible for managing the display of a screen. 294 """ 295 296 nosave = [ 297 'screen', 298 'child', 299 'children', 300 'transforms', 301 'widgets', 302 'old_widgets', 303 'hidden_widgets', 304 'old_transforms', 305 'cache', 306 'miss_cache', 307 'profile', 308 'phase', 309 'use_cache' ] 310 311 restarting = False 312 hiding = False 313 transient = False 314 315 def after_setstate(self): 316 self.screen = get_screen_variant(self.screen_name[0]) 317 self.child = None 318 self.children = [ ] 319 self.transforms = { } 320 self.widgets = { } 321 self.base_widgets = { } 322 self.old_widgets = None 323 self.old_transforms = None 324 self.hidden_widgets = { } 325 self.cache = { } 326 self.phase = UPDATE 327 self.use_cache = { } 328 self.miss_cache = { } 329 330 self.profile = profile.get(self.screen_name, None) 331 332 def __init__(self, screen, tag, layer, widget_properties={}, scope={}, transient=False, **properties): 333 334 super(ScreenDisplayable, self).__init__(**properties) 335 336 # Stash the properties, so we can re-create the screen. 337 self.properties = properties 338 339 # The screen, and it's name. (The name is used to look up the 340 # screen on save.) 341 self.screen = screen 342 self.screen_name = screen.name 343 344 self._location = self.screen.location 345 346 # The profile object that determines when we profile. 347 self.profile = profile.get(self.screen_name, None) 348 349 # The tag and layer screen was displayed with. 350 self.tag = tag 351 self.layer = layer 352 353 # The scope associated with this statement. This is passed in 354 # as keyword arguments to the displayable. 355 self.scope = renpy.python.RevertableDict(scope) 356 357 # The child associated with this screen. 358 self.child = None 359 360 # Widget properties given to this screen the last time it was 361 # shown. 362 self.widget_properties = widget_properties 363 364 # A map from name to the widget with that name. 365 self.widgets = { } 366 367 # Same, but to the widget without considering _main. 368 self.base_widgets = { } 369 370 # The persistent cache. 371 self.cache = { } 372 373 if tag and layer: 374 old_screen = get_screen(tag, layer) 375 else: 376 old_screen = None 377 378 # A map from name to the transform with that name. (This is 379 # taken from the old version of the screen, if it exists. 380 if old_screen is not None: 381 self.transforms = old_screen.transforms 382 else: 383 self.transforms = { } 384 385 # A map from a (screen name, id) pair to cache. This is for use 386 # statements with the id parameter. 387 if old_screen is not None: 388 self.use_cache = old_screen.use_cache 389 else: 390 self.use_cache = { } 391 392 # A version of the cache that's used when we have a screen that is 393 # being displayed with the same tag with a cached copy of the screen 394 # we want to display. 395 self.miss_cache = { } 396 397 # What widgets and transforms were the last time this screen was 398 # updated. Used to communicate with the ui module, and only 399 # valid during an update - not used at other times. 400 self.old_widgets = None 401 self.old_transforms = None 402 403 # Should we transfer data from the old_screen? This becomes 404 # true once this screen finishes updating for the first time, 405 # and also while we're using something. 406 self.old_transfers = (old_screen and old_screen.screen_name == self.screen_name) 407 408 # The current transform event, and the last transform event to 409 # be processed. 410 self.current_transform_event = None 411 412 # A dict-set of widgets (by id) that have been hidden from us. 413 self.hidden_widgets = { } 414 415 # Are we restarting or hiding? 416 self.restarting = False 417 self.hiding = False 418 419 # Is this a transient screen? 420 self.transient = transient 421 422 # Modal and zorder. 423 self.modal = renpy.python.py_eval(self.screen.modal, locals=self.scope) 424 self.zorder = renpy.python.py_eval(self.screen.zorder, locals=self.scope) 425 426 # The lifecycle phase we are in - one of PREDICT, SHOW, UPDATE, or HIDE. 427 self.phase = PREDICT 428 429 def __unicode__(self): 430 return "Screen {}".format(" ".join(self.screen_name)) 431 432 def visit(self): 433 return [ self.child ] 434 435 def visit_all(self, callback, seen=None): 436 callback(self) 437 438 try: 439 push_current_screen(self) 440 if self.child is not None: 441 self.child.visit_all(callback, seen=None) 442 finally: 443 pop_current_screen() 444 445 def per_interact(self): 446 renpy.display.render.redraw(self, 0) 447 self.update() 448 449 def set_transform_event(self, event): 450 super(ScreenDisplayable, self).set_transform_event(event) 451 self.current_transform_event = event 452 453 def find_focusable(self, callback, focus_name): 454 455 hiding = (self.phase == OLD) or (self.phase == HIDE) 456 457 try: 458 push_current_screen(self) 459 460 if self.child and not hiding: 461 self.child.find_focusable(callback, focus_name) 462 finally: 463 pop_current_screen() 464 465 if self.modal and not callable(self.modal): 466 raise renpy.display.layout.IgnoreLayers() 467 468 def copy(self): 469 rv = ScreenDisplayable(self.screen, self.tag, self.layer, self.widget_properties, self.scope, **self.properties) 470 rv.transforms = self.transforms.copy() 471 rv.widgets = self.widgets.copy() 472 rv.base_widgets = self.widgets.copy() 473 rv.old_transfers = True 474 rv.child = self.child 475 476 return rv 477 478 def _handles_event(self, event): 479 if self.child is None: 480 481 if self.transient: 482 return False 483 484 self.update() 485 486 return self.child._handles_event(event) 487 488 def _hide(self, st, at, kind): 489 490 if self.phase == HIDE: 491 hid = self 492 else: 493 494 if (self.child is not None) and (not self.child._handles_event(kind)): 495 return None 496 497 updated_screens.discard(self) 498 self.update() 499 500 if self.screen is None: 501 return None 502 503 if self.child is None: 504 return None 505 506 if not self.child._handles_event(kind): 507 return None 508 509 if self.screen.ast is not None: 510 self.screen.ast.copy_on_change(self.cache.get(0, {})) 511 512 hid = self.copy() 513 514 for i in self.child.children: 515 i.set_transform_event(kind) 516 517 hid.phase = HIDE 518 519 rv = None 520 521 old_child = hid.child 522 523 if not isinstance(old_child, renpy.display.layout.MultiBox): 524 return None 525 526 renpy.ui.detached() 527 hid.child = renpy.ui.default_fixed(focus="_screen_" + "_".join(self.screen_name)) 528 hid.children = [ hid.child ] 529 renpy.ui.close() 530 531 for d in old_child.children: 532 c = d._hide(st, at, kind) 533 534 if c is not None: 535 renpy.display.render.redraw(c, 0) 536 hid.child.add(c) 537 538 rv = hid 539 540 if hid is not None: 541 renpy.display.render.redraw(hid, 0) 542 543 return rv 544 545 def _in_current_store(self): 546 547 if self.screen is None: 548 return self 549 550 if self.child is None: 551 return self 552 553 if not renpy.config.transition_screens: 554 return self 555 556 if self.screen.ast is not None: 557 self.screen.ast.copy_on_change(self.cache.get(0, {})) 558 559 rv = self.copy() 560 rv.phase = OLD 561 rv.child = self.child._in_current_store() 562 563 return rv 564 565 def update(self): 566 567 if self in updated_screens: 568 return 569 570 updated_screens.add(self) 571 572 if self.screen is None: 573 self.child = renpy.display.layout.Null() 574 return { } 575 576 # Do not update if restarting or hiding. 577 if self.restarting or (self.phase == HIDE) or (self.phase == OLD): 578 if not self.child: 579 self.child = renpy.display.layout.Null() 580 581 return self.widgets 582 583 profile = False 584 debug = False 585 586 if self.profile: 587 588 if self.phase == UPDATE and self.profile.update: 589 profile = True 590 elif self.phase == SHOW and self.profile.show: 591 profile = True 592 elif self.phase == PREDICT and self.profile.predict: 593 profile = True 594 595 if renpy.display.interface.profile_once and self.profile.request: 596 profile = True 597 598 if profile: 599 profile_log.write("%s %s %s", 600 phase_name[self.phase], 601 " ".join(self.screen_name), 602 datetime.datetime.now().strftime("%H:%M:%S.%f")) 603 604 start = time.time() 605 606 if self.profile.debug: 607 debug = True 608 609 # Cycle widgets and transforms. 610 self.old_widgets = self.widgets 611 self.old_transforms = self.transforms 612 self.widgets = { } 613 self.base_widgets = { } 614 self.transforms = { } 615 616 push_current_screen(self) 617 618 old_ui_screen = renpy.ui.screen 619 renpy.ui.screen = self 620 621 # The name of the root screen of this screen. 622 NAME = 0 623 624 old_cache = self.cache.get(NAME, None) 625 626 # Evaluate the screen. 627 try: 628 629 renpy.ui.detached() 630 self.child = renpy.ui.default_fixed(focus="_screen_" + "_".join(self.screen_name)) 631 self.children = [ self.child ] 632 633 self.scope["_scope"] = self.scope 634 self.scope["_name"] = NAME 635 self.scope["_debug"] = debug 636 637 self.screen.function(**self.scope) 638 639 renpy.ui.close() 640 641 finally: 642 # Safe removal as to not reraise another exception and lose the last one 643 self.scope.pop("_scope", None) 644 645 renpy.ui.screen = old_ui_screen 646 pop_current_screen() 647 648 # Finish up. 649 self.old_widgets = None 650 self.old_transforms = None 651 self.old_transfers = True 652 653 if self.miss_cache: 654 self.miss_cache.clear() 655 656 # Deal with the case where the screen version changes. 657 if (self.cache.get(NAME, None) is not old_cache) and (self.current_transform_event is None) and (self.phase == UPDATE): 658 self.current_transform_event = "update" 659 660 if self.current_transform_event: 661 662 for i in self.child.children: 663 i.set_transform_event(self.current_transform_event) 664 665 self.current_transform_event = None 666 667 if profile: 668 end = time.time() 669 670 if self.profile.time: 671 profile_log.write("* %.2f ms", 1000 * (end - start)) 672 673 if self.profile.debug: 674 profile_log.write("\n") 675 676 return self.widgets 677 678 def render(self, w, h, st, at): 679 680 if not self.child: 681 self.update() 682 683 if self.phase == SHOW: 684 self.phase = UPDATE 685 686 try: 687 push_current_screen(self) 688 child = renpy.display.render.render(self.child, w, h, st, at) 689 finally: 690 pop_current_screen() 691 692 rv = renpy.display.render.Render(w, h) 693 rv.focus_screen = self 694 695 hiding = (self.phase == OLD) or (self.phase == HIDE) 696 697 if self.screen is None: 698 sensitive = False 699 else: 700 sensitive = renpy.python.py_eval(self.screen.sensitive, locals=self.scope) 701 702 rv.blit(child, (0, 0), focus=sensitive and not hiding, main=not hiding) 703 rv.modal = self.modal and not hiding 704 705 return rv 706 707 def get_placement(self): 708 if not self.child: 709 self.update() 710 711 return self.child.get_placement() 712 713 def event(self, ev, x, y, st): 714 715 if (self.phase == OLD) or (self.phase == HIDE): 716 return 717 718 if not self.screen: 719 return None 720 721 if not renpy.python.py_eval(self.screen.sensitive, locals=self.scope): 722 ev = renpy.display.interface.time_event 723 724 try: 725 push_current_screen(self) 726 727 rv = self.child.event(ev, x, y, st) 728 finally: 729 pop_current_screen() 730 731 if rv is not None: 732 return rv 733 734 if renpy.display.layout.check_modal(self.modal, ev, x, y, None, None): 735 raise renpy.display.layout.IgnoreLayers() 736 737 def get_phase_name(self): 738 return phase_name[self.phase] 739 740 def _tts(self): 741 if (self.phase == OLD) or (self.phase == HIDE): 742 return "" 743 744 if self.modal: 745 return renpy.display.tts.TTSDone(self._tts_common()) 746 else: 747 return self._tts_common() 748 749 750# The name of the screen that is currently being displayed, or 751# None if no screen is being currently displayed. 752_current_screen = None 753 754# The stack of old current screens. 755current_screen_stack = [ ] 756 757 758def push_current_screen(screen): 759 global _current_screen 760 current_screen_stack.append(_current_screen) 761 _current_screen = screen 762 763 764def pop_current_screen(): 765 global _current_screen 766 _current_screen = current_screen_stack.pop() 767 768 769# A map from (screen_name, variant) tuples to screen. 770screens = { } 771 772# A map from screen name to map from variant to screen. 773screens_by_name = collections.defaultdict(dict) 774 775# The screens that were updated during the current interaction. 776updated_screens = set() 777 778 779def get_screen_variant(name, candidates=None): 780 """ 781 Get a variant screen object for `name`. 782 783 `candidates` 784 A list of candidate variants. 785 """ 786 787 if candidates is None: 788 candidates = renpy.config.variants 789 790 for i in candidates: 791 rv = screens.get((name, i), None) 792 if rv is not None: 793 return rv 794 795 return None 796 797 798def get_all_screen_variants(name): 799 """ 800 Gets all variants of the screen with `name`. 801 802 Returns a list of (`variant`, `screen`) tuples, in no particular 803 order. 804 """ 805 806 if isinstance(name, basestring): 807 name = tuple(name.split()) 808 809 name = name[0] 810 811 if name not in screens_by_name: 812 return [ ] 813 814 return list(screens_by_name[name].items()) 815 816 817# Have all screens been analyzed? 818analyzed = False 819 820# Have the screens been prepared? 821prepared = False 822 823# Caches for sort_screens. 824sorted_screens = [ ] 825screens_at_sort = { } 826 827# The list of screens that participate in a use cycle. 828use_cycle = [ ] 829 830 831def sort_screens(): 832 """ 833 Produces a list of SL2 screens in topologically sorted order. 834 """ 835 836 global use_cycle 837 global sorted_screens 838 global screens_at_sort 839 840 if screens_at_sort == screens: 841 return sorted_screens 842 843 # For each screen, the set of screens it uses. 844 depends = collections.defaultdict(set) 845 846 # For each screen, the set of screens that use it. 847 reverse = collections.defaultdict(set) 848 849 names = { i[0] for i in screens } 850 851 for k, v in screens.items(): 852 853 name = k[0] 854 855 # Ensure name exists. 856 depends[name] 857 858 if not v.ast: 859 continue 860 861 def callback(uses): 862 863 if uses not in names: 864 return 865 866 depends[name].add(uses) 867 reverse[uses].add(name) 868 869 v.ast.used_screens(callback) 870 871 rv = [ ] 872 873 workset = { k for k, v in depends.items() if not len(v) } 874 875 while workset: 876 name = workset.pop() 877 rv.append(name) 878 879 for i in reverse[name]: 880 d = depends[i] 881 d.remove(name) 882 883 if not d: 884 workset.add(i) 885 886 del reverse[name] 887 888 # Store the use cycle for later reporting. 889 use_cycle = list(reverse.keys()) 890 use_cycle.sort() 891 892 sorted_screens = rv 893 screens_at_sort = dict(screens) 894 895 return rv 896 897 898def sorted_variants(): 899 """ 900 Produces a list of screen variants in topological order. 901 """ 902 903 rv = [ ] 904 905 for name in sort_screens(): 906 rv.extend(screens_by_name[name].values()) 907 908 return rv 909 910 911def analyze_screens(): 912 """ 913 Analyzes all screens. 914 """ 915 916 global analyzed 917 918 if analyzed: 919 return 920 921 for s in sorted_variants(): 922 if s.ast is None: 923 continue 924 925 s.ast.analyze_screen() 926 927 analyzed = True 928 929 930def prepare_screens(): 931 """ 932 Prepares all screens for use. 933 """ 934 935 global prepared 936 937 if prepared: 938 return 939 940 predict_cache.clear() 941 942 old_predicting = renpy.display.predict.predicting 943 renpy.display.predict.predicting = True 944 945 try: 946 947 if not analyzed: 948 analyze_screens() 949 950 for s in sorted_variants(): 951 if s.ast is None: 952 continue 953 954 s.ast.unprepare_screen() 955 s.ast.prepare_screen() 956 957 prepared = True 958 959 finally: 960 renpy.display.predict.predicting = old_predicting 961 962 if renpy.config.developer and use_cycle: 963 raise Exception("The following screens use each other in a loop: " + ", ".join(use_cycle) + ". This is not allowed.") 964 965 966def define_screen(*args, **kwargs): 967 """ 968 :doc: screens 969 :args: (name, function, modal="False", zorder="0", tag=None, variant=None) 970 971 Defines a screen with `name`, which should be a string. 972 973 `function` 974 The function that is called to display the screen. The 975 function is called with the screen scope as keyword 976 arguments. It should ignore additional keyword arguments. 977 978 The function should call the ui functions to add things to the 979 screen. 980 981 `modal` 982 A string that, when evaluated, determines of the created 983 screen should be modal. A modal screen prevents screens 984 underneath it from receiving input events. 985 986 `zorder` 987 A string that, when evaluated, should be an integer. The integer 988 controls the order in which screens are displayed. A screen 989 with a greater zorder number is displayed above screens with a 990 lesser zorder number. 991 992 `tag` 993 The tag associated with this screen. When the screen is shown, 994 it replaces any other screen with the same tag. The tag 995 defaults to the name of the screen. 996 997 `predict` 998 If true, this screen can be loaded for image prediction. If false, 999 it can't. Defaults to true. 1000 1001 `variant` 1002 String. Gives the variant of the screen to use. 1003 1004 """ 1005 1006 Screen(*args, **kwargs) 1007 1008 1009def get_screen_layer(name): 1010 """ 1011 Returns the layer that the screen with `name` is part of. 1012 """ 1013 1014 if not isinstance(name, basestring): 1015 name = name[0] 1016 1017 screen = get_screen_variant(name) 1018 1019 if screen is None: 1020 return "screens" 1021 else: 1022 return screen.layer 1023 1024 1025def get_screen(name, layer=None): 1026 """ 1027 :doc: screens 1028 1029 Returns the ScreenDisplayable with the given `name` on layer. `name` 1030 is first interpreted as a tag name, and then a screen name. If the 1031 screen is not showing, returns None. 1032 1033 This can also take a list of names, in which case the first screen 1034 that is showing is returned. 1035 1036 This function can be used to check if a screen is showing:: 1037 1038 if renpy.get_screen("say"): 1039 text "The say screen is showing." 1040 else: 1041 text "The say screen is hidden." 1042 1043 """ 1044 1045 if layer is None: 1046 layer = get_screen_layer(name) 1047 1048 if isinstance(name, basestring): 1049 name = (name,) 1050 1051 sl = renpy.exports.scene_lists() 1052 1053 for tag in name: 1054 1055 sd = sl.get_displayable_by_tag(layer, tag) 1056 if sd is not None: 1057 return sd 1058 1059 for tag in name: 1060 1061 sd = sl.get_displayable_by_name(layer, (tag,)) 1062 if sd is not None: 1063 return sd 1064 1065 return None 1066 1067 1068def has_screen(name): 1069 """ 1070 Returns true if a screen with the given name exists. 1071 """ 1072 1073 if not isinstance(name, tuple): 1074 name = tuple(name.split()) 1075 1076 if not name: 1077 return False 1078 1079 if get_screen_variant(name[0]): 1080 return True 1081 else: 1082 return False 1083 1084 1085def show_screen(_screen_name, *_args, **kwargs): 1086 """ 1087 :doc: screens 1088 1089 The programmatic equivalent of the show screen statement. 1090 1091 Shows the named screen. This takes the following keyword arguments: 1092 1093 `_screen_name` 1094 The name of the screen to show. 1095 `_layer` 1096 The layer to show the screen on. 1097 `_zorder` 1098 The zorder to show the screen on. If not specified, defaults to 1099 the zorder associated with the screen. It that's not specified, 1100 it is 0 by default. 1101 `_tag` 1102 The tag to show the screen with. If not specified, defaults to 1103 the tag associated with the screen. It that's not specified, 1104 defaults to the name of the screen. 1105 `_widget_properties` 1106 A map from the id of a widget to a property name -> property 1107 value map. When a widget with that id is shown by the screen, 1108 the specified properties are added to it. 1109 `_transient` 1110 If true, the screen will be automatically hidden at the end of 1111 the current interaction. 1112 1113 Non-keyword arguments, and keyword arguments that do not begin with 1114 an underscore, are passed to the screen. 1115 """ 1116 1117 _layer = kwargs.pop("_layer", None) 1118 _tag = kwargs.pop("_tag", None) 1119 _widget_properties = kwargs.pop("_widget_properties", {}) 1120 _transient = kwargs.pop("_transient", False) 1121 _zorder = kwargs.pop("_zorder", None) 1122 1123 name = _screen_name 1124 1125 if not isinstance(name, tuple): 1126 name = tuple(name.split()) 1127 1128 screen = get_screen_variant(name[0]) 1129 1130 if screen is None: 1131 raise Exception("Screen %s is not known.\n" % (name[0],)) 1132 1133 if _layer is None: 1134 _layer = get_screen_layer(name) 1135 1136 if _tag is None: 1137 _tag = screen.tag 1138 1139 scope = { } 1140 1141 if screen.parameters: 1142 scope["_kwargs" ] = kwargs 1143 scope["_args"] = _args 1144 else: 1145 scope.update(kwargs) 1146 1147 d = ScreenDisplayable(screen, _tag, _layer, _widget_properties, scope, transient=_transient) 1148 1149 if _zorder is None: 1150 _zorder = d.zorder 1151 1152 old_d = get_screen(_tag, _layer) 1153 1154 if old_d and old_d.cache: 1155 d.cache = old_d.cache 1156 d.miss_cache = cache_get(screen, _args, kwargs) 1157 d.phase = UPDATE 1158 else: 1159 d.cache = cache_get(screen, _args, kwargs) 1160 d.phase = SHOW 1161 1162 sls = renpy.display.core.scene_lists() 1163 1164 sls.add(_layer, d, _tag, zorder=_zorder, transient=_transient, keep_st=True, name=name) 1165 1166 1167def predict_screen(_screen_name, *_args, **kwargs): 1168 """ 1169 Predicts the displayables that make up the given screen. 1170 1171 `_screen_name` 1172 The name of the screen to show. 1173 `_widget_properties` 1174 A map from the id of a widget to a property name -> property 1175 value map. When a widget with that id is shown by the screen, 1176 the specified properties are added to it. 1177 1178 Keyword arguments not beginning with underscore (_) are used to 1179 initialize the screen's scope. 1180 """ 1181 1182 _layer = kwargs.pop("_layer", None) 1183 _tag = kwargs.pop("_tag", None) 1184 _widget_properties = kwargs.pop("_widget_properties", {}) 1185 _transient = kwargs.pop("_transient", False) 1186 1187 name = _screen_name 1188 1189 if renpy.config.debug_prediction: 1190 renpy.display.ic_log.write("Predict screen %s", name) 1191 1192 if not isinstance(name, tuple): 1193 name = tuple(name.split()) 1194 1195 screen = get_screen_variant(name[0]) 1196 1197 if screen is None: 1198 return 1199 1200 if not screen.predict: 1201 return 1202 1203 if _layer is None: 1204 _layer = get_screen_layer(name) 1205 1206 scope = { } 1207 scope["_scope"] = scope 1208 1209 if screen.parameters: 1210 scope["_kwargs" ] = kwargs 1211 scope["_args"] = _args 1212 else: 1213 scope.update(kwargs) 1214 1215 try: 1216 1217 d = ScreenDisplayable(screen, None, None, _widget_properties, scope) 1218 d.cache = cache_get(screen, _args, kwargs) 1219 d.update() 1220 cache_put(screen, _args, kwargs, d.cache) 1221 1222 renpy.display.predict.displayable(d) 1223 1224 except: 1225 if renpy.config.debug_prediction: 1226 import traceback 1227 1228 print("While predicting screen", _screen_name) 1229 traceback.print_exc() 1230 print() 1231 1232 finally: 1233 scope.pop("_scope", None) 1234 1235 renpy.ui.reset() 1236 1237 1238def hide_screen(tag, layer=None): 1239 """ 1240 :doc: screens 1241 1242 The programmatic equivalent of the hide screen statement. 1243 1244 Hides the screen with `tag` on `layer`. 1245 """ 1246 1247 if layer is None: 1248 layer = get_screen_layer((tag,)) 1249 1250 screen = get_screen(tag, layer) 1251 1252 if screen is not None: 1253 renpy.exports.hide(screen.tag, layer=layer) 1254 1255 1256def use_screen(_screen_name, *_args, **kwargs): 1257 1258 _name = kwargs.pop("_name", ()) 1259 _scope = kwargs.pop("_scope", { }) 1260 1261 name = _screen_name 1262 1263 if not isinstance(name, tuple): 1264 name = tuple(name.split()) 1265 1266 screen = get_screen_variant(name[0]) 1267 1268 if screen is None: 1269 raise Exception("Screen %r is not known." % (name,)) 1270 1271 old_transfers = _current_screen.old_transfers 1272 _current_screen.old_transfers = True 1273 1274 if screen.parameters: 1275 scope = { } 1276 scope["_kwargs"] = kwargs 1277 scope["_args"] = _args 1278 else: 1279 scope = _scope.copy() 1280 scope.update(kwargs) 1281 1282 scope["_scope"] = scope 1283 scope["_name"] = (_name, name) 1284 1285 try: 1286 screen.function(**scope) 1287 finally: 1288 scope.pop("_scope", None) 1289 1290 _current_screen.old_transfers = old_transfers 1291 1292 1293def current_screen(): 1294 return _current_screen 1295 1296 1297def get_displayable(screen, id, layer=None, base=False): # @ReservedAssignment 1298 """ 1299 :doc: screens 1300 :name: renpy.get_displayable 1301 1302 From the `screen` on `layer`, returns the displayable with 1303 `id`. Returns None if the screen doesn't exist, or there is no 1304 widget with that id on the screen. 1305 """ 1306 1307 if isinstance(screen, ScreenDisplayable): 1308 screen = screen.screen_name 1309 1310 if screen is None: 1311 screen = current_screen() 1312 else: 1313 if layer is None: 1314 layer = get_screen_layer(screen) 1315 1316 screen = get_screen(screen, layer) 1317 1318 if not isinstance(screen, ScreenDisplayable): 1319 return None 1320 1321 if screen.child is None: 1322 screen.update() 1323 1324 if base: 1325 rv = screen.base_widgets.get(id, None) 1326 else: 1327 rv = screen.widgets.get(id, None) 1328 1329 return rv 1330 1331 1332get_widget = get_displayable 1333 1334 1335def get_displayable_properties(id, screen=None, layer=None): # @ReservedAssignment 1336 """ 1337 :doc: screens 1338 :name: renpy.get_displayable_properties 1339 1340 Returns the properties for the displayable with `id` in the `screen` 1341 on `layer`. If `screen` is None, returns the properties for the 1342 current screen. This can be used from Python or property code inside 1343 a screen. 1344 1345 Note that this returns a dictionary containing the widget properties, 1346 and so to get an individual property, the dictionary must be accessed. 1347 """ 1348 1349 if screen is None: 1350 s = current_screen() 1351 else: 1352 if layer is None: 1353 layer = get_screen_layer(screen) 1354 1355 s = get_screen(screen, layer) 1356 1357 if s is None: 1358 return { } 1359 1360 rv = s.widget_properties.get(id, None) 1361 1362 if rv is None: 1363 rv = { } 1364 1365 return rv 1366 1367 1368get_widget_properties = get_displayable_properties 1369 1370 1371def before_restart(): 1372 """ 1373 This is called before Ren'Py restarts to put the screens into restart 1374 mode, which prevents crashes due to variables being used that are no 1375 longer defined. 1376 """ 1377 1378 for k, layer in renpy.display.interface.old_scene.items(): 1379 if k is None: 1380 continue 1381 1382 for i in layer.children: 1383 if isinstance(i, ScreenDisplayable): 1384 i.restarting = True 1385 1386 1387def show_overlay_screens(suppress_overlay): 1388 """ 1389 Called from interact to show or hide the overlay screens. 1390 """ 1391 1392 show = not suppress_overlay 1393 1394 if renpy.store._overlay_screens is None: 1395 show = show 1396 elif renpy.store._overlay_screens is True: 1397 show = True 1398 else: 1399 show = False 1400 1401 if show: 1402 1403 for i in renpy.config.overlay_screens: 1404 if get_screen(i) is None: 1405 show_screen(i) 1406 1407 else: 1408 1409 for i in renpy.config.overlay_screens: 1410 if get_screen(i) is not None: 1411 hide_screen(i) 1412 1413 1414def per_frame(): 1415 """ 1416 Called from interact once per frame to invalidate screens we want to 1417 update once per frame. 1418 """ 1419 1420 for i in renpy.config.per_frame_screens: 1421 s = get_screen(i) 1422 1423 if s is None: 1424 continue 1425 1426 updated_screens.discard(s) 1427 renpy.display.render.invalidate(s) 1428 s.update() 1429