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