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
25# This file contains displayables that move, zoom, rotate, or otherwise
26# transform displayables. (As well as displayables that support them.)
27import math
28import types # @UnresolvedImport
29
30import renpy.display # @UnusedImport
31from renpy.display.layout import Container
32
33from renpy.display.accelerator import transform_render
34from renpy.atl import position, any_object, bool_or_none, float_or_none, matrix, mesh
35
36# The null object that's used if we don't have a defined child.
37null = None
38
39
40def get_null():
41    global null
42
43    if null is None:
44        null = renpy.display.layout.Null()
45        renpy.display.motion.null = null
46
47    return null
48
49# Convert a position from cartesian to polar coordinates.
50
51
52def cartesian_to_polar(x, y, xaround, yaround):
53    """
54    Converts cartesian coordinates to polar coordinates.
55    """
56
57    dx = x - xaround
58    dy = y - yaround
59
60    radius = math.hypot(dx, dy)
61    angle = math.atan2(dx, -dy) / math.pi * 180
62
63    if angle < 0:
64        angle += 360
65
66    return angle, radius
67
68
69def polar_to_cartesian(angle, radius, xaround, yaround):
70    """
71    Converts polart coordinates to cartesian coordinates.
72    """
73
74    angle = angle * math.pi / 180
75
76    dx = radius * math.sin(angle)
77    dy = -radius * math.cos(angle)
78
79    x = type(xaround)(xaround + dx)
80    y = type(yaround)(yaround + dy)
81
82    return x, y
83
84
85def first_not_none(*args):
86    """
87    Returns the first argument that is not None.
88    """
89
90    for i in args:
91        if i is not None:
92            return i
93    return i
94
95
96class TransformState(renpy.object.Object):
97
98    last_angle = None
99
100    def __init__(self):
101
102        # Most fields on this object are set by add_property, at the bottom
103        # of this file.
104
105        # An xpos (etc) inherited from our child overrides an xpos inherited
106        # from an old transform, but not an xpos set in the current transform.
107        #
108        # inherited_xpos stores the inherited_xpos, which is overridden by the
109        # xpos, if not None.
110        self.inherited_xpos = None
111        self.inherited_ypos = None
112        self.inherited_xanchor = None
113        self.inherited_yanchor = None
114
115        # The last angle that was rotated to.
116        self.last_angle = None
117
118    def take_state(self, ts):
119
120        d = self.__dict__
121
122        for k in all_properties:
123            d[k] = getattr(ts, k)
124
125        self.last_angle = ts.last_angle
126
127        # Set the position and anchor to None, so inheritance works.
128        if self.perspective is None:
129            self.xpos = None
130            self.ypos = None
131            self.xanchor = None
132            self.yanchor = None
133
134        # Take the computed position properties, not the
135        # raw ones.
136        (self.inherited_xpos,
137         self.inherited_ypos,
138         self.inherited_xanchor,
139         self.inherited_yanchor,
140         _,
141         _,
142         _) = ts.get_placement()
143
144        self.xoffset = ts.xoffset
145        self.yoffset = ts.yoffset
146        self.subpixel = ts.subpixel
147
148    # Returns a dict, with p -> (old, new) where p is a property that
149    # has changed between this object and the new object.
150    def diff(self, newts):
151
152        rv = { }
153
154        for prop in diff2_properties:
155            new = getattr(newts, prop)
156            old = getattr(self, prop)
157
158            if new != old:
159                rv[prop] = (old, new)
160
161        for prop in diff4_properties:
162
163            new = getattr(newts, prop)
164            old = getattr(self, prop)
165
166            if new is None:
167                new = getattr(newts, "inherited_" + prop)
168            if old is None:
169                old = getattr(self, "inherited_" + prop)
170
171            if new != old:
172                rv[prop] = (old, new)
173
174        return rv
175
176    def get_placement(self, cxoffset=0, cyoffset=0):
177
178        if self.perspective is not None:
179            return (
180                0,
181                0,
182                0,
183                0,
184                cxoffset,
185                cyoffset,
186                False,
187                )
188
189        return (
190            first_not_none(self.xpos, self.inherited_xpos),
191            first_not_none(self.ypos, self.inherited_ypos),
192            first_not_none(self.xanchor, self.inherited_xanchor),
193            first_not_none(self.yanchor, self.inherited_yanchor),
194            self.xoffset + cxoffset,
195            self.yoffset + cyoffset,
196            self.subpixel,
197            )
198
199    # These update various properties.
200    def get_xalign(self):
201        return self.xpos
202
203    def set_xalign(self, v):
204        self.xpos = v
205        self.xanchor = v
206
207    xalign = property(get_xalign, set_xalign)
208
209    def get_yalign(self):
210        return self.ypos
211
212    def set_yalign(self, v):
213        self.ypos = v
214        self.yanchor = v
215
216    yalign = property(get_yalign, set_yalign)
217
218    def get_around(self):
219        return (self.xaround, self.yaround)
220
221    def set_around(self, value):
222        self.xaround, self.yaround = value
223        self.xanchoraround, self.yanchoraround = None, None
224
225    def set_alignaround(self, value):
226        self.xaround, self.yaround = value
227        self.xanchoraround, self.yanchoraround = value
228
229    around = property(get_around, set_around)
230    alignaround = property(get_around, set_alignaround)
231
232    def get_angle(self):
233        xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
234        ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
235        angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
236        return angle
237
238    def get_radius(self):
239        xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
240        ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
241        _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
242        return radius
243
244    def set_angle(self, value):
245        xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
246        ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
247        _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
248        angle = value
249        self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
250
251        if self.xanchoraround:
252            self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
253
254    def set_radius(self, value):
255        xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
256        ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
257        angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
258        radius = value
259        self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
260
261        if self.xanchoraround:
262            self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
263
264    angle = property(get_angle, set_angle)
265    radius = property(get_radius, set_radius)
266
267    def get_pos(self):
268        return self.xpos, self.ypos
269
270    def set_pos(self, value):
271        self.xpos, self.ypos = value
272
273    pos = property(get_pos, set_pos)
274
275    def get_anchor(self):
276        return self.xanchor, self.yanchor
277
278    def set_anchor(self, value):
279        self.xanchor, self.yanchor = value
280
281    anchor = property(get_anchor, set_anchor)
282
283    def get_align(self):
284        return self.xpos, self.ypos
285
286    def set_align(self, value):
287        self.xanchor, self.yanchor = value
288        self.xpos, self.ypos = value
289
290    align = property(get_align, set_align)
291
292    def get_offset(self):
293        return self.xoffset, self.yoffset
294
295    def set_offset(self, value):
296        self.xoffset, self.yoffset = value
297
298    offset = property(get_offset, set_offset)
299
300    def get_xysize(self):
301        return self.xsize, self.ysize
302
303    def set_xysize(self, value):
304        if value is None:
305            value = (None, None)
306        self.xsize, self.ysize = value
307
308    xysize = property(get_xysize, set_xysize)
309
310    def set_size(self, value):
311        if value is None:
312            self.xysize = None
313        else:
314            self.xysize = tuple(int(x) if isinstance(x, float) else x for x in value)
315
316    size = property(get_xysize, set_size)
317
318    def set_xcenter(self, value):
319        self.xpos = value
320        self.xanchor = 0.5
321
322    def get_xcenter(self):
323        return self.xpos
324
325    def set_ycenter(self, value):
326        self.ypos = value
327        self.yanchor = 0.5
328
329    def get_ycenter(self):
330        return self.ypos
331
332    xcenter = property(get_xcenter, set_xcenter)
333    ycenter = property(get_ycenter, set_ycenter)
334
335    def get_xycenter(self):
336        return self.xcenter, self.ycenter
337
338    def set_xycenter(self, value):
339        if value is None:
340            value = (None, None)
341        self.xcenter, self.ycenter = value
342
343    xycenter = property(get_xycenter, set_xycenter)
344
345
346class Proxy(object):
347    """
348    This class proxies a field from the transform to its state.
349    """
350
351    def __init__(self, name):
352        self.name = name
353
354    def __get__(self, instance, owner):
355        return getattr(instance.state, self.name)
356
357    def __set__(self, instance, value):
358        return setattr(instance.state, self.name, value)
359
360
361class Transform(Container):
362    """
363    Documented in sphinx, because we can't scan this object.
364    """
365
366    __version__ = 5
367    transform_event_responder = True
368
369    def after_upgrade(self, version):
370
371        if version < 1:
372            self.active = False
373            self.state = TransformState()
374
375            self.state.xpos = self.xpos or 0
376            self.state.ypos = self.ypos or 0
377            self.state.xanchor = self.xanchor or 0
378            self.state.yanchor = self.yanchor or 0
379            self.state.alpha = self.alpha
380            self.state.rotate = self.rotate
381            self.state.zoom = self.zoom
382            self.state.xzoom = self.xzoom
383            self.state.yzoom = self.yzoom
384
385            self.hide_request = False
386            self.hide_response = True
387
388        if version < 2:
389            self.st = 0
390            self.at = 0
391
392        if version < 3:
393            self.st_offset = 0
394            self.at_offset = 0
395            self.child_st_base = 0
396
397        if version < 4:
398            self.style_arg = 'transform'
399
400        if version < 5:
401            self.replaced_request = False
402            self.replaced_response = True
403
404    DEFAULT_ARGUMENTS = {
405        "selected_activate" : { },
406        "selected_hover" : { },
407        "selected_idle" : { },
408        "selected_insensitive" : { },
409        "activate" : { },
410        "hover" : { },
411        "idle" : { },
412        "insensitive" : { },
413        "" : { },
414        }
415
416    # Compatibility with old versions of the class.
417    active = False
418    children = False
419    arguments = DEFAULT_ARGUMENTS
420
421    # Default before we set this.
422    child_size = (0, 0)
423
424    def __init__(self,
425                 child=None,
426                 function=None,
427                 style="default",
428                 focus=None,
429                 default=False,
430                 _args=None,
431
432                 **kwargs):
433
434        self.kwargs = kwargs
435        self.style_arg = style
436
437        super(Transform, self).__init__(style=style, focus=focus, default=default, _args=_args)
438
439        self.function = function
440
441        child = renpy.easy.displayable_or_none(child)
442        if child is not None:
443            self.add(child)
444
445        self.state = TransformState()
446
447        if kwargs:
448
449            # A map from prefix -> (prop -> value)
450            self.arguments = { }
451
452            # Fill self.arguments with a
453            for k, v in kwargs.items():
454
455                prefix = ""
456                prop = k
457
458                while True:
459
460                    if prop in renpy.atl.PROPERTIES and (not prefix or prefix in Transform.DEFAULT_ARGUMENTS):
461
462                        if prefix not in self.arguments:
463                            self.arguments[prefix] = { }
464
465                        self.arguments[prefix][prop] = v
466                        break
467
468                    new_prefix, _, prop = prop.partition("_")
469
470                    if not prop:
471                        raise Exception("Unknown transform property: %r" % k)
472
473                    if prefix:
474                        prefix = prefix + "_" + new_prefix
475                    else:
476                        prefix = new_prefix
477
478            if "" in self.arguments:
479                for k, v in self.arguments[""].items():
480                    setattr(self.state, k, v)
481
482        else:
483            self.arguments = None
484
485        # This is the matrix transforming our coordinates into child coordinates.
486        self.forward = None
487
488        # Have we called the function at least once?
489        self.active = False
490
491        # Have we been requested to hide?
492        self.hide_request = False
493
494        # True if it's okay for us to hide.
495        self.hide_response = True
496
497        # Have we been requested to replaced?
498        self.replaced_request = False
499
500        # True if it's okay for us to replaced.
501        self.replaced_response = True
502
503        self.st = 0
504        self.at = 0
505        self.st_offset = 0
506        self.at_offset = 0
507
508        self.child_st_base = 0
509
510    def visit(self):
511        if self.child is None:
512            return [ ]
513        else:
514            return [ self.child ]
515
516    # The default function chooses entries from self.arguments that match
517    # the style prefix, and applies them to the state.
518    def default_function(self, state, st, at):
519
520        if self.arguments is None:
521            return None
522
523        prefix = self.style.prefix.strip("_")
524        prefixes = [ ]
525
526        while prefix:
527            prefixes.insert(0, prefix)
528            _, _, prefix = prefix.partition("_")
529
530        prefixes.insert(0, "")
531
532        for i in prefixes:
533            d = self.arguments.get(i, None)
534
535            if d is None:
536                continue
537
538            for k, v in d.items():
539                setattr(state, k, v)
540
541        return None
542
543    def set_transform_event(self, event):
544        if self.child is not None:
545            self.child.set_transform_event(event)
546            self.last_child_transform_event = event
547
548        super(Transform, self).set_transform_event(event)
549
550    def take_state(self, t):
551        """
552        Takes the transformation state from object t into this object.
553        """
554
555        if self is t:
556            return
557
558        if not isinstance(t, Transform):
559            return
560
561        self.state.take_state(t.state)
562
563        if isinstance(self.child, Transform) and isinstance(t.child, Transform):
564            self.child.take_state(t.child)
565
566        if (self.child is None) and (t.child is not None):
567            self.add(t.child)
568            self.child_st_base = t.child_st_base
569
570        # The arguments will be applied when the default function is
571        # called.
572
573    def take_execution_state(self, t):
574        """
575        Takes the execution state from object t into this object. This is
576        overridden by renpy.atl.TransformBase.
577        """
578
579        if self is t:
580            return
581
582        if not isinstance(t, Transform):
583            return
584
585        self.hide_request = t.hide_request
586        self.replaced_request = t.replaced_request
587
588        self.state.xpos = t.state.xpos
589        self.state.ypos = t.state.ypos
590        self.state.xanchor = t.state.xanchor
591        self.state.yanchor = t.state.yanchor
592
593        self.child_st_base = t.child_st_base
594
595        if isinstance(self.child, Transform) and isinstance(t.child, Transform):
596            self.child.take_execution_state(t.child)
597
598    def copy(self):
599        """
600        Makes a copy of this transform.
601        """
602
603        d = self()
604        d.kwargs = { }
605        d.take_state(self)
606        d.take_execution_state(self)
607        d.st = self.st
608        d.at = self.at
609
610        return d
611
612    def _change_transform_child(self, child):
613        rv = self.copy()
614
615        if self.child is not None:
616            rv.set_child(self.child._change_transform_child(child))
617
618        return rv
619
620    def _handles_event(self, event):
621
622        if (event == "replaced") and (not self.active):
623            return True
624
625        if self.function is not None:
626            return True
627
628        if self.child and self.child._handles_event(event):
629            return True
630
631        return False
632
633    def _hide(self, st, at, kind):
634
635        # Prevent time from ticking backwards, as can happen if we replace a
636        # transform but keep its state.
637        if st + self.st_offset <= self.st:
638            self.st_offset = self.st - st
639        if at + self.at_offset <= self.at:
640            self.at_offset = self.at - at
641
642        self.st = st = st + self.st_offset
643        self.at = at = at + self.at_offset
644
645        if not self.active:
646            self.update_state()
647
648        if not self.child:
649            return None
650
651        if not (self.hide_request or self.replaced_request):
652            d = self.copy()
653        else:
654            d = self
655
656        d.st_offset = self.st_offset
657        d.at_offset = self.at_offset
658
659        if isinstance(self, ATLTransform):
660            d.atl_st_offset = self.atl_st_offset if (self.atl_st_offset is not None) else self.st_offset
661
662        if kind == "hide":
663            d.hide_request = True
664        else:
665            d.replaced_request = True
666
667        d.hide_response = True
668        d.replaced_response = True
669
670        if d.function is not None:
671            d.function(d, st, at)
672        elif isinstance(d, ATLTransform):
673            d.execute(d, st, at)
674
675        new_child = d.child._hide(st - self.st_offset, at - self.st_offset, kind)
676
677        if new_child is not None:
678            d.child = new_child
679            d.hide_response = False
680            d.replaced_response = False
681
682        if (not d.hide_response) or (not d.replaced_response):
683            renpy.display.render.redraw(d, 0)
684            return d
685
686        return None
687
688    def set_child(self, child, duplicate=True):
689
690        child = renpy.easy.displayable(child)
691
692        if duplicate and child._duplicatable:
693            child = child._duplicate(self._args)
694            child._unique()
695
696        if child._duplicatable:
697            self._duplicatable = True
698
699        self.child = child
700        self.children = [ child ]
701
702        self.child_st_base = self.st
703
704        child.per_interact()
705
706        renpy.display.render.invalidate(self)
707
708    def update_state(self):
709        """
710        This updates the state to that at self.st, self.at.
711        """
712
713        # NOTE: This function is duplicated (more or less) in ATLTransform.
714
715        self.hide_response = True
716        self.replaced_response = True
717
718        # If we have to, call the function that updates this transform.
719        if self.arguments is not None:
720            self.default_function(self, self.st, self.at)
721
722        if self.function is not None:
723            fr = self.function(self, self.st, self.at)
724
725            # Order a redraw, if necessary.
726            if fr is not None:
727                renpy.display.render.redraw(self, fr)
728
729        self.active = True
730
731    # The render method is now defined in accelerator.pyx.
732    def render(self, width, height, st, at):
733        return transform_render(self, width, height, st, at)
734
735    def event(self, ev, x, y, st):
736
737        if self.hide_request:
738            return None
739
740        if not self.state.events:
741            return
742
743        children = self.children
744        offsets = self.offsets
745
746        if not offsets:
747            return None
748
749        for i in range(len(self.children) - 1, -1, -1):
750
751            d = children[i]
752            xo, yo = offsets[i]
753
754            cx = x - xo
755            cy = y - yo
756
757            # Transform screen coordinates to child coordinates.
758            cx, cy = self.forward.transform(cx, cy)
759
760            rv = d.event(ev, cx, cy, st)
761            if rv is not None:
762                return rv
763
764        return None
765
766    def __call__(self, child=None, take_state=True, _args=None):
767
768        if child is None:
769            child = self.child
770
771        if (child is not None) and (child._duplicatable):
772            child = child._duplicate(_args)
773
774        rv = Transform(
775            child=child,
776            function=self.function,
777            style=self.style_arg,
778            _args=_args,
779            **self.kwargs)
780
781        rv.take_state(self)
782
783        return rv
784
785    def _unique(self):
786        if self.child and self.child._duplicatable:
787            self._duplicatable = True
788        else:
789            self._duplicatable = False
790
791    def get_placement(self):
792
793        if not self.active:
794            self.update_state()
795
796        if self.child is not None:
797            cxpos, cypos, cxanchor, cyanchor, cxoffset, cyoffset, csubpixel = self.child.get_placement()
798
799            # Use non-None elements of the child placement as defaults.
800            state = self.state
801
802            if renpy.config.transform_uses_child_position:
803
804                if cxpos is not None:
805                    state.inherited_xpos = cxpos
806                if cxanchor is not None:
807                    state.inherited_xanchor = cxanchor
808                if cypos is not None:
809                    state.inherited_ypos = cypos
810                if cyanchor is not None:
811                    state.inherited_yanchor = cyanchor
812
813                state.subpixel |= csubpixel
814
815        else:
816            cxoffset = 0
817            cyoffset = 0
818
819        cxoffset = cxoffset or 0
820        cyoffset = cyoffset or 0
821
822        rv = self.state.get_placement(cxoffset, cyoffset)
823
824        if self.state.transform_anchor:
825
826            xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = rv
827            if (xanchor is not None) and (yanchor is not None):
828
829                cw, ch = self.child_size
830                rw, rh = self.render_size
831
832                if xanchor.__class__ is float:
833                    xanchor *= cw
834                if yanchor.__class__ is float:
835                    yanchor *= ch
836
837                xanchor -= cw / 2.0
838                yanchor -= ch / 2.0
839
840                xanchor, yanchor = self.reverse.transform(xanchor, yanchor)
841
842                xanchor += rw / 2.0
843                yanchor += rh / 2.0
844
845                xanchor = renpy.display.core.absolute(xanchor)
846                yanchor = renpy.display.core.absolute(yanchor)
847
848                rv = (xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel)
849
850        return rv
851
852    def update(self):
853        """
854        This should be called when a transform property field is updated outside
855        of the callback method, to ensure that the change takes effect.
856        """
857
858        renpy.display.render.invalidate(self)
859
860    _duplicatable = True
861
862    def _duplicate(self, args):
863
864        if args and args.args:
865            args.extraneous()
866
867        if not self._duplicatable:
868            return self
869
870        rv = self(_args=args)
871        rv.take_execution_state(self)
872        rv._unique()
873
874        return rv
875
876    def _in_current_store(self):
877
878        if self.child is None:
879            return self
880
881        child = self.child._in_current_store()
882        if child is self.child:
883            return self
884        rv = self()
885        rv.take_execution_state(self)
886        rv.child = child
887        rv._unique()
888
889        return rv
890
891
892class ATLTransform(renpy.atl.ATLTransformBase, Transform):
893
894    def __init__(self, atl, child=None, context={}, parameters=None, **properties):
895        renpy.atl.ATLTransformBase.__init__(self, atl, context, parameters)
896        Transform.__init__(self, child=child, **properties)
897
898        self.raw_child = self.child
899
900    def update_state(self):
901        """
902        This updates the state to that at self.st, self.at.
903        """
904
905        self.hide_response = True
906        self.replaced_response = True
907
908        fr = self.execute(self, self.st, self.at)
909
910        # Order a redraw, if necessary.
911        if fr is not None:
912            renpy.display.render.redraw(self, fr)
913
914        self.active = True
915
916    def __repr__(self):
917        return "<ATL Transform {:x} {!r}>".format(id(self), self.atl.loc)
918
919
920# Names of transform properties, and if the property should be handles with
921# diff2 or diff2.
922all_properties = set()
923diff2_properties = set()
924diff4_properties = set()
925
926# Uniforms and GL properties.
927uniforms = set()
928gl_properties = set()
929
930
931def add_property(name, atl=any_object, default=None, diff=2):
932    """
933    Adds an ATL property.
934    """
935
936    if name in all_properties:
937        return
938
939    all_properties.add(name)
940    setattr(TransformState, name, default)
941    setattr(Transform, name, Proxy(name))
942    renpy.atl.PROPERTIES[name] = atl
943
944    if diff == 2:
945        diff2_properties.add(name)
946    elif diff == 4:
947        diff4_properties.add(name)
948
949
950def add_uniform(name):
951    """
952    Adds a uniform with `name` to Transform and ATL.
953    """
954
955    if not name.startswith("u_"):
956        return
957
958    if name in renpy.gl2.gl2draw.standard_uniforms:
959        return
960
961    add_property(name, diff=2)
962
963    uniforms.add(name)
964
965
966def add_gl_property(name):
967    """
968    Adds a GL property with `name` to Transform and ATL.
969    """
970
971    add_property(name, diff=None)
972
973    gl_properties.add(name)
974
975
976add_property("additive", float, 0.0)
977add_property("alpha", float, 1.0)
978add_property("blend", any_object, None)
979add_property("blur", float_or_none, None)
980add_property("corner1", (float, float), None)
981add_property("corner2", (float, float), None)
982add_property("crop", (float, float, float, float), None)
983add_property("crop_relative", bool, False)
984add_property("debug", any_object, None)
985add_property("delay", float, 0)
986add_property("events", bool, True)
987add_property("fit", str, None)
988add_property("matrixanchor", (position, position), None)
989add_property("matrixcolor", matrix, None)
990add_property("matrixtransform", matrix, None)
991add_property("maxsize", (int, int), None)
992add_property("mesh", mesh, False, diff=None)
993add_property("mesh_pad", any_object, None)
994add_property("nearest", bool_or_none, None)
995add_property("perspective", any_object, None)
996add_property("rotate", float, None)
997add_property("rotate_pad", bool, True)
998add_property("shader", any_object, None, diff=None)
999add_property("subpixel", bool, False)
1000add_property("transform_anchor", bool, False)
1001add_property("zoom", float, 1.0)
1002
1003add_property("xanchoraround", float, 0.0)
1004add_property("xanchor", position, None, diff=4)
1005add_property("xaround", position, 0.0)
1006add_property("xoffset", float, 0.0)
1007add_property("xpan", float_or_none, None)
1008add_property("xpos", position, None, diff=4)
1009add_property("xsize", position, None)
1010add_property("xtile", int, 1)
1011add_property("xzoom", float, 1.0)
1012
1013add_property("yanchoraround", float, 0.0)
1014add_property("yanchor", position, None, diff=4)
1015add_property("yaround", position, 0.0)
1016add_property("yoffset", float, 0.0)
1017add_property("ypan", float_or_none, None)
1018add_property("ypos", position, None, diff=4)
1019add_property("ysize", position, None)
1020add_property("ytile", int, 1)
1021add_property("yzoom", float, 1.0)
1022
1023add_property("zpos", float, 0.0)
1024add_property("zzoom", bool, False)
1025
1026add_gl_property("gl_anisotropic")
1027add_gl_property("gl_blend_func")
1028add_gl_property("gl_color_mask")
1029add_gl_property("gl_depth")
1030add_gl_property("gl_mipmap")
1031add_gl_property("gl_pixel_perfect")
1032add_gl_property("gl_texture_scaling")
1033add_gl_property("gl_texture_wrap")
1034
1035ALIASES = {
1036    "alignaround" : (float, float),
1037    "align" : (float, float),
1038    "anchor" : (position, position),
1039    "angle" : float,
1040    "around" : (position, position),
1041    "offset" : (int, int),
1042    "pos" : (position, position),
1043    "radius" : float,
1044    "size" : (int, int),
1045    "xalign" : float,
1046    "xcenter" : position,
1047    "xycenter" : (position, position),
1048    "xysize" : (position, position),
1049    "yalign" : float,
1050    "ycenter" : position,
1051    }
1052
1053renpy.atl.PROPERTIES.update(ALIASES)
1054
1055for name in ALIASES:
1056    setattr(Transform, name, Proxy(name))
1057