CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/display/transform.py
2025-03-03 23:00:33 +01:00

1049 lines
28 KiB
Python

# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
# This file contains displayables that move, zoom, rotate, or otherwise
# transform displayables. (As well as displayables that support them.)
import math
import types # @UnresolvedImport
import renpy.display # @UnusedImport
from renpy.display.layout import Container
import renpy.display.accelerator
# The null object that's used if we don't have a defined child.
null = None
def get_null():
global null
if null is None:
null = renpy.display.layout.Null()
renpy.display.motion.null = null
return null
# Convert a position from cartesian to polar coordinates.
def cartesian_to_polar(x, y, xaround, yaround):
"""
Converts cartesian coordinates to polar coordinates.
"""
dx = x - xaround
dy = y - yaround
radius = math.hypot(dx, dy)
angle = math.atan2(dx, -dy) / math.pi * 180
if angle < 0:
angle += 360
return angle, radius
def polar_to_cartesian(angle, radius, xaround, yaround):
"""
Converts polart coordinates to cartesian coordinates.
"""
angle = angle * math.pi / 180
dx = radius * math.sin(angle)
dy = -radius * math.cos(angle)
x = type(xaround)(xaround + dx)
y = type(yaround)(yaround + dy)
return x, y
def first_not_none(*args):
"""
Returns the first argument that is not None.
"""
for i in args:
if i is not None:
return i
return i
class TransformState(renpy.object.Object):
nearest = None
xoffset = None
yoffset = None
inherited_xpos = None
inherited_ypos = None
inherited_xanchor = None
inherited_yanchor = None
transform_anchor = False
additive = 0.0
debug = None
events = True
crop_relative = False
xpan = None
ypan = None
xtile = 1
ytile = 1
last_angle = None
maxsize = None
def __init__(self):
self.alpha = 1
self.nearest = None
self.additive = 0.0
self.rotate = None
self.rotate_pad = True
self.transform_anchor = False
self.zoom = 1
self.xzoom = 1
self.yzoom = 1
self.xpos = None
self.ypos = None
self.xanchor = None
self.yanchor = None
self.xoffset = 0
self.yoffset = 0
self.xaround = 0.0
self.yaround = 0.0
self.xanchoraround = 0.0
self.yanchoraround = 0.0
self.xpan = None
self.ypan = None
self.xtile = 1
self.ytile = 1
self.subpixel = False
self.crop = None
self.crop_relative = False
self.corner1 = None
self.corner2 = None
self.size = None
self.maxsize = None
self.delay = 0
self.debug = None
self.events = True
# Note: When adding a new property, we need to add it to:
# - take_state
# - diff
# - renpy.atl.PROPERTIES
# - Proxies in Transform
# An xpos (etc) inherited from our child overrides an xpos inherited
# from an old transform, but not an xpos set in the current transform.
#
# inherited_xpos stores the inherited_xpos, which is overridden by the
# xpos, if not None.
self.inherited_xpos = None
self.inherited_ypos = None
self.inherited_xanchor = None
self.inherited_yanchor = None
def take_state(self, ts):
self.nearest = ts.nearest
self.alpha = ts.alpha
self.additive = ts.additive
self.rotate = ts.rotate
self.rotate_pad = ts.rotate_pad
self.transform_anchor = ts.transform_anchor
self.zoom = ts.zoom
self.xzoom = ts.xzoom
self.yzoom = ts.yzoom
self.xaround = ts.xaround
self.yaround = ts.yaround
self.xanchoraround = ts.xanchoraround
self.yanchoraround = ts.yanchoraround
self.crop = ts.crop
self.crop_relative = ts.crop_relative
self.corner1 = ts.corner1
self.corner2 = ts.corner2
self.size = ts.size
self.maxsize = ts.maxsize
self.xpan = ts.xpan
self.ypan = ts.ypan
self.xtile = ts.xtile
self.ytile = ts.ytile
self.last_angle = ts.last_angle
self.debug = ts.debug
self.events = ts.events
# Take the computed position properties, not the
# raw ones.
(self.inherited_xpos,
self.inherited_ypos,
self.inherited_xanchor,
self.inherited_yanchor,
_,
_,
_) = ts.get_placement()
self.xoffset = ts.xoffset
self.yoffset = ts.yoffset
self.subpixel = ts.subpixel
# Returns a dict, with p -> (old, new) where p is a property that
# has changed between this object and the new object.
def diff(self, newts):
rv = { }
def diff2(prop, new, old):
if new != old:
rv[prop] = (old, new)
def diff4(prop, new, inherited_new, old, inherited_old):
if new is None:
new_value = inherited_new
else:
new_value = new
if old is None:
old_value = inherited_old
else:
old_value = old
if new_value != old_value:
rv[prop] = (old_value, new_value)
diff2("nearest", newts.nearest, self.nearest)
diff2("alpha", newts.alpha, self.alpha)
diff2("additive", newts.additive, self.additive)
diff2("rotate", newts.rotate, self.rotate)
diff2("rotate_pad", newts.rotate_pad, self.rotate_pad)
diff2("transform_anchor", newts.transform_anchor, self.transform_anchor)
diff2("zoom", newts.zoom, self.zoom)
diff2("xzoom", newts.xzoom, self.xzoom)
diff2("yzoom", newts.yzoom, self.yzoom)
diff2("xaround", newts.xaround, self.xaround)
diff2("yaround", newts.yaround, self.yaround)
diff2("xanchoraround", newts.xanchoraround, self.xanchoraround)
diff2("yanchoraround", newts.yanchoraround, self.yanchoraround)
diff2("subpixel", newts.subpixel, self.subpixel)
diff2("crop", newts.crop, self.crop)
diff2("crop_relative", newts.crop_relative, self.crop_relative)
diff2("corner1", newts.corner1, self.corner1)
diff2("corner2", newts.corner2, self.corner2)
diff2("size", newts.size, self.size)
diff2("maxsize", newts.maxsize, self.maxsize)
diff4("xpos", newts.xpos, newts.inherited_xpos, self.xpos, self.inherited_xpos)
diff4("xanchor", newts.xanchor, newts.inherited_xanchor, self.xanchor, self.inherited_xanchor)
diff2("xoffset", newts.xoffset, self.xoffset)
diff4("ypos", newts.ypos, newts.inherited_ypos, self.ypos, self.inherited_ypos)
diff4("yanchor", newts.yanchor, newts.inherited_yanchor, self.yanchor, self.inherited_yanchor)
diff2("yoffset", newts.yoffset, self.yoffset)
diff2("xpan", newts.xpan, self.xpan)
diff2("ypan", newts.ypan, self.ypan)
diff2("xtile", newts.xtile, self.xtile)
diff2("ytile", newts.ytile, self.ytile)
diff2("debug", newts.debug, self.debug)
diff2("events", newts.events, self.events)
return rv
def get_placement(self, cxoffset=0, cyoffset=0):
return (
first_not_none(self.xpos, self.inherited_xpos),
first_not_none(self.ypos, self.inherited_ypos),
first_not_none(self.xanchor, self.inherited_xanchor),
first_not_none(self.yanchor, self.inherited_yanchor),
self.xoffset + cxoffset,
self.yoffset + cyoffset,
self.subpixel,
)
# These update various properties.
def get_xalign(self):
return self.xpos
def set_xalign(self, v):
self.xpos = v
self.xanchor = v
xalign = property(get_xalign, set_xalign)
def get_yalign(self):
return self.ypos
def set_yalign(self, v):
self.ypos = v
self.yanchor = v
yalign = property(get_yalign, set_yalign)
def get_around(self):
return (self.xaround, self.yaround)
def set_around(self, value):
self.xaround, self.yaround = value
self.xanchoraround, self.yanchoraround = None, None
def set_alignaround(self, value):
self.xaround, self.yaround = value
self.xanchoraround, self.yanchoraround = value
around = property(get_around, set_around)
alignaround = property(get_around, set_alignaround)
def get_angle(self):
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
return angle
def get_radius(self):
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
_angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
return radius
def set_angle(self, value):
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
_angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
angle = value
self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
if self.xanchoraround:
self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
def set_radius(self, value):
xpos = first_not_none(self.xpos, self.inherited_xpos, 0)
ypos = first_not_none(self.ypos, self.inherited_ypos, 0)
angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
radius = value
self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
if self.xanchoraround:
self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
angle = property(get_angle, set_angle)
radius = property(get_radius, set_radius)
def get_pos(self):
return self.xpos, self.ypos
def set_pos(self, value):
self.xpos, self.ypos = value
pos = property(get_pos, set_pos)
def get_anchor(self):
return self.xanchor, self.yanchor
def set_anchor(self, value):
self.xanchor, self.yanchor = value
anchor = property(get_anchor, set_anchor)
def get_align(self):
return self.xpos, self.ypos
def set_align(self, value):
self.xanchor, self.yanchor = value
self.xpos, self.ypos = value
align = property(get_align, set_align)
def get_offset(self):
return self.xoffset, self.yoffset
def set_offset(self, value):
self.xoffset, self.yoffset = value
offset = property(get_offset, set_offset)
def set_xcenter(self, value):
self.xpos = value
self.xanchor = 0.5
def get_xcenter(self):
return self.xpos
def set_ycenter(self, value):
self.ypos = value
self.yanchor = 0.5
def get_ycenter(self):
return self.ypos
xcenter = property(get_xcenter, set_xcenter)
ycenter = property(get_ycenter, set_ycenter)
class Proxy(object):
"""
This class proxies a field from the transform to its state.
"""
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return getattr(instance.state, self.name)
def __set__(self, instance, value):
return setattr(instance.state, self.name, value)
class Transform(Container):
"""
Documented in sphinx, because we can't scan this object.
"""
__version__ = 5
transform_event_responder = True
# Proxying things over to our state.
nearest = Proxy("nearest")
alpha = Proxy("alpha")
additive = Proxy("additive")
rotate = Proxy("rotate")
rotate_pad = Proxy("rotate_pad")
transform_anchor = Proxy("transform_anchor")
zoom = Proxy("zoom")
xzoom = Proxy("xzoom")
yzoom = Proxy("yzoom")
xpos = Proxy("xpos")
ypos = Proxy("ypos")
xanchor = Proxy("xanchor")
yanchor = Proxy("yanchor")
xalign = Proxy("xalign")
yalign = Proxy("yalign")
around = Proxy("around")
alignaround = Proxy("alignaround")
angle = Proxy("angle")
radius = Proxy("radius")
xaround = Proxy("xaround")
yaround = Proxy("yaround")
xanchoraround = Proxy("xanchoraround")
yanchoraround = Proxy("yanchoraround")
pos = Proxy("pos")
anchor = Proxy("anchor")
align = Proxy("align")
crop = Proxy("crop")
crop_relative = Proxy("crop_relative")
corner1 = Proxy("corner1")
corner2 = Proxy("corner2")
size = Proxy("size")
maxsize = Proxy("maxsize")
delay = Proxy("delay")
xoffset = Proxy("xoffset")
yoffset = Proxy("yoffset")
offset = Proxy("offset")
subpixel = Proxy("subpixel")
xcenter = Proxy("xcenter")
ycenter = Proxy("ycenter")
xpan = Proxy("xpan")
ypan = Proxy("ypan")
xtile = Proxy("xtile")
ytile = Proxy("ytile")
debug = Proxy("debug")
events = Proxy("events")
def after_upgrade(self, version):
if version < 1:
self.active = False
self.state = TransformState()
self.state.xpos = self.xpos or 0
self.state.ypos = self.ypos or 0
self.state.xanchor = self.xanchor or 0
self.state.yanchor = self.yanchor or 0
self.state.alpha = self.alpha
self.state.rotate = self.rotate
self.state.zoom = self.zoom
self.state.xzoom = self.xzoom
self.state.yzoom = self.yzoom
self.hide_request = False
self.hide_response = True
if version < 2:
self.st = 0
self.at = 0
if version < 3:
self.st_offset = 0
self.at_offset = 0
self.child_st_base = 0
if version < 4:
self.style_arg = 'transform'
if version < 5:
self.replaced_request = False
self.replaced_response = True
DEFAULT_ARGUMENTS = {
"selected_activate" : { },
"selected_hover" : { },
"selected_idle" : { },
"selected_insensitive" : { },
"activate" : { },
"hover" : { },
"idle" : { },
"insensitive" : { },
"" : { },
}
# Compatibility with old versions of the class.
active = False
children = False
arguments = DEFAULT_ARGUMENTS
# Default before we set this.
child_size = (0, 0)
def __init__(self,
child=None,
function=None,
style="default",
focus=None,
default=False,
_args=None,
**kwargs):
self.kwargs = kwargs
self.style_arg = style
super(Transform, self).__init__(style=style, focus=focus, default=default, _args=_args)
self.function = function
child = renpy.easy.displayable_or_none(child)
if child is not None:
self.add(child)
self.state = TransformState()
if kwargs:
# A map from prefix -> (prop -> value)
self.arguments = { }
# Fill self.arguments with a
for k, v in kwargs.iteritems():
prefix = ""
prop = k
while True:
if prop in renpy.atl.PROPERTIES and (not prefix or prefix in Transform.DEFAULT_ARGUMENTS):
if prefix not in self.arguments:
self.arguments[prefix] = { }
self.arguments[prefix][prop] = v
break
new_prefix, _, prop = prop.partition("_")
if not prop:
raise Exception("Unknown transform property: %r" % k)
if prefix:
prefix = prefix + "_" + new_prefix
else:
prefix = new_prefix
if "" in self.arguments:
for k, v in self.arguments[""].iteritems():
setattr(self.state, k, v)
else:
self.arguments = None
# This is the matrix transforming our coordinates into child coordinates.
self.forward = None
# Have we called the function at least once?
self.active = False
# Have we been requested to hide?
self.hide_request = False
# True if it's okay for us to hide.
self.hide_response = True
# Have we been requested to replaced?
self.replaced_request = False
# True if it's okay for us to replaced.
self.replaced_response = True
self.st = 0
self.at = 0
self.st_offset = 0
self.at_offset = 0
self.child_st_base = 0
def visit(self):
if self.child is None:
return [ ]
else:
return [ self.child ]
# The default function chooses entries from self.arguments that match
# the style prefix, and applies them to the state.
def default_function(self, state, st, at):
if self.arguments is None:
return None
prefix = self.style.prefix.strip("_")
prefixes = [ ]
while prefix:
prefixes.insert(0, prefix)
_, _, prefix = prefix.partition("_")
prefixes.insert(0, "")
for i in prefixes:
d = self.arguments.get(i, None)
if d is None:
continue
for k, v in d.iteritems():
setattr(state, k, v)
return None
def set_transform_event(self, event):
if self.child is not None:
self.child.set_transform_event(event)
self.last_child_transform_event = event
super(Transform, self).set_transform_event(event)
def take_state(self, t):
"""
Takes the transformation state from object t into this object.
"""
if self is t:
return
if not isinstance(t, Transform):
return
self.state.take_state(t.state)
if isinstance(self.child, Transform) and isinstance(t.child, Transform):
self.child.take_state(t.child)
if (self.child is None) and (t.child is not None):
self.add(t.child)
self.child_st_base = t.child_st_base
# The arguments will be applied when the default function is
# called.
def take_execution_state(self, t):
"""
Takes the execution state from object t into this object. This is
overridden by renpy.atl.TransformBase.
"""
if self is t:
return
if not isinstance(t, Transform):
return
self.hide_request = t.hide_request
self.replaced_request = t.replaced_request
self.state.xpos = t.state.xpos
self.state.ypos = t.state.ypos
self.state.xanchor = t.state.xanchor
self.state.yanchor = t.state.yanchor
self.child_st_base = t.child_st_base
if isinstance(self.child, Transform) and isinstance(t.child, Transform):
self.child.take_execution_state(t.child)
def copy(self):
"""
Makes a copy of this transform.
"""
d = self()
d.kwargs = { }
d.take_state(self)
d.take_execution_state(self)
d.st = self.st
d.at = self.at
return d
def _change_transform_child(self, child):
rv = self.copy()
if self.child is not None:
rv.set_child(self.child._change_transform_child(child))
return rv
def _handles_event(self, event):
if self.function is not None:
return True
if self.child and self.child._handles_event(event):
return True
return False
def _hide(self, st, at, kind):
if not self.child:
return None
# Prevent time from ticking backwards, as can happen if we replace a
# transform but keep its state.
if st + self.st_offset <= self.st:
self.st_offset = self.st - st
if at + self.at_offset <= self.at:
self.at_offset = self.at - at
self.st = st = st + self.st_offset
self.at = at = at + self.at_offset
if not (self.hide_request or self.replaced_request):
d = self.copy()
else:
d = self
d.st_offset = self.st_offset
d.at_offset = self.at_offset
if not (self.hide_request or self.replaced_request):
d.atl_st_offset = None
if kind == "hide":
d.hide_request = True
else:
d.replaced_request = True
d.hide_response = True
d.replaced_response = True
if d.function is not None:
d.function(d, st + d.st_offset, at + d.at_offset)
elif isinstance(d, ATLTransform):
d.execute(d, st + d.st_offset, at + d.at_offset)
new_child = d.child._hide(st, at, kind)
if new_child is not None:
d.child = new_child
d.hide_response = False
d.replaced_response = False
if (not d.hide_response) or (not d.replaced_response):
renpy.display.render.redraw(d, 0)
return d
return None
def set_child(self, child, duplicate=True):
child = renpy.easy.displayable(child)
if duplicate and child._duplicatable:
child = child._duplicate(self._args)
child._unique()
if child._duplicatable:
self._duplicatable = True
self.child = child
self.children = [ child ]
self.child_st_base = self.st
child.per_interact()
renpy.display.render.invalidate(self)
def update_state(self):
"""
This updates the state to that at self.st, self.at.
"""
# NOTE: This function is duplicated (more or less) in ATLTransform.
self.hide_response = True
self.replaced_response = True
# If we have to, call the function that updates this transform.
if self.arguments is not None:
self.default_function(self, self.st, self.at)
if self.function is not None:
fr = self.function(self, self.st, self.at)
# Order a redraw, if necessary.
if fr is not None:
renpy.display.render.redraw(self, fr)
self.active = True
# The render method is now defined in accelerator.pyx.
def event(self, ev, x, y, st):
if self.hide_request:
return None
if not self.state.events:
return
children = self.children
offsets = self.offsets
if not offsets:
return None
for i in xrange(len(self.children)-1, -1, -1):
d = children[i]
xo, yo = offsets[i]
cx = x - xo
cy = y - yo
# Transform screen coordinates to child coordinates.
cx, cy = self.forward.transform(cx, cy)
rv = d.event(ev, cx, cy, st)
if rv is not None:
return rv
return None
def __call__(self, child=None, take_state=True, _args=None):
if child is None:
child = self.child
if (child is not None) and (child._duplicatable):
child = child._duplicate(_args)
rv = Transform(
child=child,
function=self.function,
style=self.style_arg,
_args=_args,
**self.kwargs)
rv.take_state(self)
return rv
def _unique(self):
if self.child and self.child._duplicatable:
self._duplicatable = True
else:
self._duplicatable = False
def get_placement(self):
if not self.active:
self.update_state()
if self.child is not None:
cxpos, cypos, cxanchor, cyanchor, cxoffset, cyoffset, csubpixel = self.child.get_placement()
# Use non-None elements of the child placement as defaults.
state = self.state
if renpy.config.transform_uses_child_position:
if cxpos is not None:
state.inherited_xpos = cxpos
if cxanchor is not None:
state.inherited_xanchor = cxanchor
if cypos is not None:
state.inherited_ypos = cypos
if cyanchor is not None:
state.inherited_yanchor = cyanchor
state.subpixel |= csubpixel
else:
cxoffset = 0
cyoffset = 0
cxoffset = cxoffset or 0
cyoffset = cyoffset or 0
rv = self.state.get_placement(cxoffset, cyoffset)
if self.state.transform_anchor:
xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = rv
if (xanchor is not None) and (yanchor is not None):
cw, ch = self.child_size
rw, rh = self.render_size
if xanchor.__class__ is float:
xanchor *= cw
if yanchor.__class__ is float:
yanchor *= ch
xanchor -= cw / 2.0
yanchor -= ch / 2.0
xanchor, yanchor = self.reverse.transform(xanchor, yanchor)
xanchor += rw / 2.0
yanchor += rh / 2.0
xanchor = renpy.display.core.absolute(xanchor)
yanchor = renpy.display.core.absolute(yanchor)
rv = (xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel)
return rv
def update(self):
"""
This should be called when a transform property field is updated outside
of the callback method, to ensure that the change takes effect.
"""
renpy.display.render.invalidate(self)
_duplicatable = True
def _duplicate(self, args):
if args and args.args:
args.extraneous()
if not self._duplicatable:
return self
rv = self(_args=args)
rv.take_execution_state(self)
rv._unique()
return rv
def _in_current_store(self):
if self.child is None:
return self
child = self.child._in_current_store()
if child is self.child:
return self
rv = self()
rv.take_execution_state(self)
rv.child = child
rv._unique()
return rv
def _show(self):
self.update_state()
Transform.render = types.MethodType(renpy.display.accelerator.transform_render, None, Transform)
class ATLTransform(renpy.atl.ATLTransformBase, Transform):
def __init__(self, atl, child=None, context={}, parameters=None, **properties):
renpy.atl.ATLTransformBase.__init__(self, atl, context, parameters)
Transform.__init__(self, child=child, **properties)
self.raw_child = self.child
def update_state(self):
"""
This updates the state to that at self.st, self.at.
"""
self.hide_response = True
self.replaced_response = True
fr = self.execute(self, self.st, self.at)
# Order a redraw, if necessary.
if fr is not None:
renpy.display.render.redraw(self, fr)
self.active = True
def __repr__(self):
return "<ATL Transform {:x} {!r}>".format(id(self), self.atl.loc)
def _show(self):
super(ATLTransform, self)._show()
self.execute(self, self.st, self.at)