1007 lines
26 KiB
Python
1007 lines
26 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.
|
|
|
|
# This file contains some miscellaneous displayables that involve images.
|
|
# Most of the guts of this file have been moved into im.py, with only some
|
|
# of the stuff thar uses images remaining.
|
|
|
|
from __future__ import print_function
|
|
|
|
import renpy.display
|
|
import renpy.text
|
|
from renpy.display.render import render, Render
|
|
|
|
import collections
|
|
|
|
# A map from image name to the displayable object corresponding to that
|
|
# image.
|
|
images = { }
|
|
|
|
# A map from image tag to lists of possible attributes for images with that
|
|
# tag.
|
|
image_attributes = collections.defaultdict(dict)
|
|
|
|
# The set of image names Ren'Py knows about, as strings with spaces.
|
|
image_names = [ ]
|
|
|
|
|
|
def list_images():
|
|
"""
|
|
:doc: image_func
|
|
|
|
Returns a list of images that have been added to Ren'Py, as a list of
|
|
strings with spaces between the name components.
|
|
"""
|
|
|
|
return image_names
|
|
|
|
|
|
def get_available_image_tags():
|
|
"""
|
|
:doc: image_func
|
|
|
|
Returns a list of image tags that have been defined.
|
|
"""
|
|
|
|
return [ k for k, v in image_attributes.items() if v ]
|
|
|
|
|
|
def get_available_image_attributes(tag, attributes=()):
|
|
"""
|
|
:undocumented:
|
|
|
|
Returns a list of tuples, with each tuple representing a possible
|
|
combination of image attributes that can be associated with `tag`.
|
|
If `attributes` is given, only images that contain all the attributes
|
|
in that iterable are returned.
|
|
"""
|
|
|
|
rv = [ ]
|
|
|
|
if tag not in image_attributes:
|
|
return rv
|
|
|
|
for at in image_attributes[tag]:
|
|
for a in attributes:
|
|
if a not in at:
|
|
break
|
|
else:
|
|
rv.append(at)
|
|
|
|
return rv
|
|
|
|
|
|
def get_tag_method(tag, method):
|
|
"""
|
|
This looks for an image named `tag`, then tries to grab the given method
|
|
from it. If it can do that, returns a reference to the method. Otherwise,
|
|
it returns None.
|
|
"""
|
|
|
|
ti = images.get((tag,), None)
|
|
|
|
if ti is None:
|
|
return None
|
|
|
|
return getattr(ti, method, None)
|
|
|
|
|
|
def check_image_attributes(tag, attributes):
|
|
"""
|
|
:doc: image_func
|
|
|
|
Checks to see if there is a unique image with the given tag and
|
|
attributes. If there is, returns the attributes in order.
|
|
Otherwise, returns None.
|
|
"""
|
|
|
|
l = [ ]
|
|
|
|
for attrs, d in image_attributes[tag].items():
|
|
|
|
remainder = [ i for i in attributes if i not in attrs ]
|
|
|
|
ca = getattr(d, "_choose_attributes", None)
|
|
|
|
if ca is not None:
|
|
|
|
chosen = ca(tag, remainder, None)
|
|
if chosen is not None:
|
|
l.append(list(attrs) + list(chosen))
|
|
|
|
else:
|
|
|
|
if not remainder:
|
|
l.append(attrs)
|
|
|
|
# Check to see if there's an image that is exactly the one we want.
|
|
for i in l:
|
|
if len(i) == len(attributes):
|
|
return tuple(i)
|
|
|
|
if len(l) != 1:
|
|
return None
|
|
|
|
return tuple(l[0])
|
|
|
|
|
|
def get_ordered_image_attributes(tag, attributes=(), sort=None):
|
|
"""
|
|
:doc: image_func
|
|
|
|
Returns a list of image tags, ordered in a way that makes sense to
|
|
present to the user.
|
|
|
|
`attributes`
|
|
If present, only attributes that are compatible with the given
|
|
attributes are considered. (Compatible means that the attributes
|
|
can be in a single image at the same time.)
|
|
|
|
`sort`
|
|
If not None, the returned list of attributes is sorted. This is a function
|
|
that should be used as a tiebreaker.
|
|
|
|
"""
|
|
|
|
sequences = [ ]
|
|
|
|
attrcount = collections.defaultdict(int)
|
|
attrtotalpos = collections.defaultdict(float)
|
|
|
|
for attrs, d in sorted(image_attributes[tag].items()):
|
|
|
|
la = getattr(d, "_list_attributes", None)
|
|
if la is not None:
|
|
|
|
sequence = list(attrs) + la(tag, [ i for i in attributes if i not in attrs ])
|
|
|
|
if not all(i in sequence for i in attributes):
|
|
continue
|
|
|
|
sequences.append(sequence)
|
|
|
|
else:
|
|
|
|
if not all(i in attrs for i in attributes):
|
|
continue
|
|
|
|
for i, attr in enumerate(attrs):
|
|
attrcount[attr] += 1
|
|
attrtotalpos[attr] += i
|
|
|
|
if sort is None:
|
|
return list(set(attrcount.keys()) | set(j for i in sequences for j in i))
|
|
|
|
# If we have a sequence, do a topological sort on the before-after relation -
|
|
# with an ajustment to make sure it will complete even if it loops.
|
|
|
|
rv = [ ]
|
|
|
|
# A map from an attribute to all the attributes it is after.
|
|
after = collections.defaultdict(set)
|
|
|
|
for i in sequences:
|
|
while i:
|
|
|
|
j = i.pop(0)
|
|
|
|
# Ensure it exists.
|
|
after[j]
|
|
|
|
for k in i:
|
|
after[k].add(j)
|
|
|
|
while after:
|
|
|
|
mincount = min(len(i) for i in after.values())
|
|
ready = set(k for k, v in after.items() if len(v) == mincount)
|
|
|
|
for i in ready:
|
|
del after[i]
|
|
|
|
for k in after:
|
|
after[k] = after[k] - ready
|
|
|
|
ready = list(ready)
|
|
ready.sort(key=lambda a : (sort(a), a))
|
|
|
|
rv.extend(ready)
|
|
|
|
l = [ ]
|
|
|
|
for attr in attrcount:
|
|
if attr not in rv:
|
|
l.append((attrtotalpos[attr] / attrcount[attr], sort(attr), attr))
|
|
|
|
l.sort()
|
|
for i in l:
|
|
rv.append(i[2])
|
|
|
|
return rv
|
|
|
|
|
|
def register_image(name, d):
|
|
"""
|
|
Registers the existence of an image with `name`, and that the image
|
|
used displayable d.
|
|
|
|
`name`
|
|
A tuple of strings.
|
|
"""
|
|
|
|
tag = name[0]
|
|
rest = name[1:]
|
|
|
|
images[name] = d
|
|
image_attributes[tag][rest] = d
|
|
|
|
image_names.append(" ".join(name))
|
|
|
|
|
|
def image_exists(name, exact=False):
|
|
"""
|
|
:doc: image_func
|
|
:name: renpy.has_image
|
|
|
|
Return true if an image with `name` exists, and false if no such image
|
|
exists.
|
|
|
|
`name`
|
|
Either a string giving an image name, or a tuple of strings giving
|
|
the name components.
|
|
|
|
`exact`
|
|
Returns true if and only if an image with the exact name exists -
|
|
parameterized matches are not included.
|
|
"""
|
|
|
|
if not isinstance(name, tuple):
|
|
name = tuple(name.split())
|
|
|
|
while name:
|
|
if name in images:
|
|
return True
|
|
|
|
if exact:
|
|
return False
|
|
|
|
name = name[:-1]
|
|
|
|
return False
|
|
|
|
|
|
def get_registered_image(name):
|
|
"""
|
|
:doc: image_func
|
|
|
|
If an image with the same name has been registered with renpy.register_image,
|
|
returns it. Otherwise, returns None.
|
|
"""
|
|
|
|
if not isinstance(name, tuple):
|
|
name = tuple(name.split())
|
|
|
|
return images.get(name)
|
|
|
|
|
|
def wrap_render(child, w, h, st, at):
|
|
rend = render(child, w, h, st, at)
|
|
rv = Render(rend.width, rend.height)
|
|
rv.blit(rend, (0, 0))
|
|
return rv
|
|
|
|
|
|
class ImageReference(renpy.display.core.Displayable):
|
|
"""
|
|
ImageReference objects are used to reference images by their name,
|
|
which is a tuple of strings corresponding to the name used to define
|
|
the image in an image statment.
|
|
"""
|
|
|
|
nosave = [ 'target' ]
|
|
|
|
target = None
|
|
old_transform = None
|
|
param_target = None
|
|
|
|
__version__ = 1
|
|
|
|
def after_upgrade(self, version):
|
|
if version < 1:
|
|
if isinstance(self.param_target, renpy.display.transform.Transform):
|
|
self.old_transform = self.param_target
|
|
|
|
def __init__(self, name, **properties):
|
|
"""
|
|
@param name: A tuple of strings, the name of the image. Or else
|
|
a displayable, containing the image directly.
|
|
"""
|
|
|
|
super(ImageReference, self).__init__(**properties)
|
|
|
|
self.name = name
|
|
|
|
def __unicode__(self):
|
|
return u"<ImageReference {!r}>".format(self.name)
|
|
|
|
def __hash__(self):
|
|
return hash(self.name)
|
|
|
|
def __eq__(self, o):
|
|
if self is o:
|
|
return True
|
|
|
|
if not self._equals(o):
|
|
return False
|
|
|
|
if self.name != o.name:
|
|
return False
|
|
|
|
return True
|
|
|
|
def _target(self):
|
|
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target._target()
|
|
|
|
def find_target(self):
|
|
|
|
name = self.name
|
|
|
|
if isinstance(name, renpy.display.core.Displayable):
|
|
self.target = name
|
|
return True
|
|
|
|
if not isinstance(name, tuple):
|
|
name = tuple(name.split())
|
|
|
|
def error(msg):
|
|
self.target = renpy.text.text.Text(msg, color=(255, 0, 0, 255), xanchor=0, xpos=0, yanchor=0, ypos=0)
|
|
|
|
if renpy.config.debug:
|
|
raise Exception(msg)
|
|
|
|
args = [ ]
|
|
|
|
while name:
|
|
target = images.get(name, None)
|
|
|
|
if target is not None:
|
|
break
|
|
|
|
args.insert(0, name[-1])
|
|
name = name[:-1]
|
|
|
|
if not name:
|
|
error("Image '%s' not found." % ' '.join(self.name))
|
|
return False
|
|
|
|
if name and (self._args.name == name):
|
|
error("Image '{}' refers to itself.".format(' '.join(name)))
|
|
return False
|
|
|
|
args += self._args.args
|
|
|
|
try:
|
|
|
|
a = self._args.copy(name=name, args=args)
|
|
|
|
self.target = target._duplicate(a)
|
|
|
|
except Exception as e:
|
|
|
|
if renpy.config.debug:
|
|
raise
|
|
|
|
error(str(e))
|
|
|
|
# Copy the old transform over.
|
|
new_transform = self.target._target()
|
|
|
|
if isinstance(new_transform, renpy.display.transform.Transform):
|
|
if self.old_transform is not None:
|
|
new_transform.take_state(self.old_transform)
|
|
|
|
self.old_transform = new_transform
|
|
|
|
else:
|
|
self.old_transform = None
|
|
|
|
return True
|
|
|
|
_duplicatable = True
|
|
|
|
def _duplicate(self, args):
|
|
|
|
if args and args.args:
|
|
args.extraneous()
|
|
|
|
rv = self._copy(args)
|
|
rv.target = None
|
|
|
|
if isinstance(rv.name, renpy.display.core.Displayable):
|
|
if rv.name._duplicatable:
|
|
rv.name = rv.name._duplicate(args)
|
|
|
|
rv.find_target()
|
|
rv._duplicatable = rv.target._duplicatable
|
|
|
|
return rv
|
|
|
|
def _unique(self):
|
|
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
self._duplicatable = self.target._duplicatable
|
|
|
|
def _in_current_store(self):
|
|
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
target = self.target._in_current_store()
|
|
|
|
if target is self.target:
|
|
return self
|
|
|
|
rv = self._copy()
|
|
rv.target = target
|
|
return rv
|
|
|
|
def _handles_event(self, event):
|
|
if self.target is None:
|
|
return False
|
|
|
|
return self.target._handles_event(event)
|
|
|
|
def _hide(self, st, at, kind):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target._hide(st, at, kind)
|
|
|
|
def set_transform_event(self, event):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target.set_transform_event(event)
|
|
|
|
def event(self, ev, x, y, st):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target.event(ev, x, y, st)
|
|
|
|
def render(self, width, height, st, at):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return wrap_render(self.target, width, height, st, at)
|
|
|
|
def get_placement(self):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
if not renpy.config.imagereference_respects_position:
|
|
return self.target.get_placement()
|
|
|
|
xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.target.get_placement()
|
|
|
|
if xpos is None:
|
|
xpos = self.style.xpos
|
|
|
|
if ypos is None:
|
|
ypos = self.style.ypos
|
|
|
|
if xanchor is None:
|
|
xanchor = self.style.xanchor
|
|
|
|
if yanchor is None:
|
|
yanchor = self.style.yanchor
|
|
|
|
return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
|
|
|
|
def visit(self):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return [ self.target ]
|
|
|
|
|
|
class DynamicImage(renpy.display.core.Displayable):
|
|
"""
|
|
:doc: disp_imagelike
|
|
:args: (name)
|
|
|
|
A DynamicImage is a displayable that has text interpolation performed
|
|
on it to yield a string giving a new displayable. Such interpolation is
|
|
performed at the start of each interaction.
|
|
"""
|
|
|
|
nosave = [ 'raw_target' ]
|
|
|
|
# The target that this image currently resolves to.
|
|
target = None
|
|
|
|
# The raw target that the image resolves to, before it has been parameterized.
|
|
raw_target = None
|
|
|
|
# Have we been locked, so we never change?
|
|
locked = False
|
|
|
|
# The name used for hashing.
|
|
hash_name = None
|
|
|
|
def __init__(self, name, scope=None, **properties):
|
|
super(DynamicImage, self).__init__(**properties)
|
|
|
|
self.name = name
|
|
|
|
if isinstance(name, basestring) and ("[prefix_" in name):
|
|
self._duplicatable = True
|
|
|
|
if isinstance(name, list):
|
|
for i in name:
|
|
if ("[prefix_" in i):
|
|
self._duplicatable = True
|
|
|
|
if scope is not None:
|
|
self.find_target(scope)
|
|
self._uses_scope = True
|
|
else:
|
|
self._uses_scope = False
|
|
|
|
def _scope(self, scope, update):
|
|
return self.find_target(scope, update)
|
|
|
|
def __unicode__(self):
|
|
return u"DynamicImage {!r}".format(self.name)
|
|
|
|
def __hash__(self):
|
|
|
|
if self.hash_name is None:
|
|
if isinstance(self.name, list):
|
|
self.hash_name = tuple(self.name)
|
|
else:
|
|
self.hash_name = self.name
|
|
|
|
return hash(self.hash_name)
|
|
|
|
def __eq__(self, o):
|
|
if self is o:
|
|
return True
|
|
|
|
if not self._equals(o):
|
|
return False
|
|
|
|
if self.name != o.name:
|
|
return False
|
|
|
|
if self._uses_scope and (self.target != o.target):
|
|
return False
|
|
|
|
return True
|
|
|
|
def _target(self):
|
|
if self.target:
|
|
return self.target._target()
|
|
else:
|
|
return self
|
|
|
|
def set_style_prefix(self, prefix, root):
|
|
|
|
if (prefix != self.style.prefix) and self._duplicatable:
|
|
self.target = None
|
|
self.raw_target = None
|
|
|
|
super(DynamicImage, self).set_style_prefix(prefix, root)
|
|
|
|
def find_target(self, scope=None, update=True):
|
|
|
|
if self.locked and (self.target is not None):
|
|
return
|
|
|
|
if self._args.prefix is None:
|
|
if self._duplicatable:
|
|
prefix = self.style.prefix
|
|
else:
|
|
prefix = ""
|
|
else:
|
|
prefix = self._args.prefix
|
|
|
|
try:
|
|
search = [ ]
|
|
target = renpy.easy.dynamic_image(self.name, scope, prefix=prefix, search=search)
|
|
except KeyError as ke:
|
|
raise Exception("In DynamicImage %r: Could not find substitution '%s'." % (self.name, unicode(ke.args[0])))
|
|
except Exception as e:
|
|
raise Exception("In DynamicImage %r: %r" % (self.name, e))
|
|
|
|
if target is None:
|
|
error = "DynamicImage %r: could not find image." % (self.name,)
|
|
|
|
if len(search) == 1:
|
|
error += " (%r)" % (search[0],)
|
|
elif len(search) == 2:
|
|
error += " (%r, %r)" % (search[0], search[1])
|
|
elif len(search) > 2:
|
|
error += " (%r, %r, and %d more.)" % (search[0], search[1], len(search) - 2)
|
|
|
|
raise Exception(error)
|
|
|
|
if self.raw_target == target:
|
|
return False
|
|
|
|
if not update:
|
|
return True
|
|
|
|
self.raw_target = target
|
|
old_target = self.target
|
|
|
|
if target._duplicatable:
|
|
target = target._duplicate(self._args)
|
|
|
|
self.target = target
|
|
|
|
renpy.display.render.redraw(self, 0)
|
|
|
|
if not old_target:
|
|
return True
|
|
|
|
if not isinstance(old_target, renpy.display.motion.Transform):
|
|
return True
|
|
|
|
if not isinstance(target, renpy.display.motion.Transform):
|
|
self.target = target = renpy.display.motion.Transform(child=target)
|
|
|
|
target.take_state(old_target)
|
|
|
|
return True
|
|
|
|
def _duplicate(self, args):
|
|
|
|
if args and args.args:
|
|
args.extraneous()
|
|
|
|
rv = self._copy(args)
|
|
rv.target = None
|
|
rv.raw_target = None
|
|
# This does not set _duplicatable, since it should always remain the
|
|
# same.
|
|
return rv
|
|
|
|
def _in_current_store(self):
|
|
rv = self._copy()
|
|
|
|
if rv.target:
|
|
rv.target = rv.target._in_current_store()
|
|
|
|
rv.locked = True
|
|
return rv
|
|
|
|
def _handles_event(self, event):
|
|
if self.target is None:
|
|
return False
|
|
|
|
return self.target._handles_event(event)
|
|
|
|
def _hide(self, st, at, kind):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target._hide(st, at, kind)
|
|
|
|
def set_transform_event(self, event):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target.set_transform_event(event)
|
|
|
|
def event(self, ev, x, y, st):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target.event(ev, x, y, st)
|
|
|
|
def render(self, width, height, st, at):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return wrap_render(self.target, width, height, st, at)
|
|
|
|
def get_placement(self):
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return self.target.get_placement()
|
|
|
|
def visit(self):
|
|
|
|
if self.target is None:
|
|
self.find_target()
|
|
|
|
return [ self.target ]
|
|
|
|
def per_interact(self):
|
|
old_target = self.target
|
|
|
|
if not self._uses_scope:
|
|
self.find_target()
|
|
|
|
if old_target is not self.target:
|
|
self.target.visit_all(lambda i : i.per_interact())
|
|
|
|
|
|
class ShownImageInfo(renpy.object.Object):
|
|
"""
|
|
This class keeps track of which images are being shown right now,
|
|
and what the attributes of those images are. (It's used for a similar
|
|
purpose during prediction, regarding the state in the future.)
|
|
"""
|
|
|
|
__version__ = 2
|
|
|
|
def __init__(self, old=None):
|
|
"""
|
|
Creates a new object. If `old` is given, copies the default state
|
|
from old, otherwise initializes the object to a default state.
|
|
"""
|
|
|
|
if old is None:
|
|
|
|
# A map from (layer, tag) -> tuple of attributes
|
|
# This doesn't necessarily correspond to something that is
|
|
# currently showing, as we can remember the state of a tag
|
|
# for use in SideImage.
|
|
self.attributes = { }
|
|
|
|
# A set of (layer, tag) pairs that are being shown on the
|
|
# screen right now.
|
|
self.shown = set()
|
|
|
|
else:
|
|
self.attributes = old.attributes.copy()
|
|
self.shown = old.shown.copy()
|
|
|
|
def after_upgrade(self, version):
|
|
if version < 2:
|
|
|
|
self.attributes = { }
|
|
self.shown = set()
|
|
|
|
for layer in self.images:
|
|
for tag in self.images[layer]:
|
|
self.attributes[layer, tag] = self.images[layer][tag][1:]
|
|
self.shown.add((layer, tag))
|
|
|
|
def get_attributes(self, layer, tag, default=()):
|
|
"""
|
|
Get the attributes associated the image with tag on the given
|
|
layer.
|
|
"""
|
|
|
|
if layer is None:
|
|
layer = renpy.config.tag_layer.get(tag, "master")
|
|
|
|
return self.attributes.get((layer, tag), default)
|
|
|
|
def showing(self, layer, name, exact=False):
|
|
"""
|
|
Returns true if name is the prefix of an image that is showing
|
|
on layer, or false otherwise.
|
|
"""
|
|
|
|
tag = name[0]
|
|
rest = name[1:]
|
|
|
|
if layer is None:
|
|
layer = renpy.config.tag_layer.get(tag, "master")
|
|
|
|
if (layer, tag) not in self.shown:
|
|
return None
|
|
|
|
shown = self.attributes[layer, tag]
|
|
|
|
if len(shown) < len(rest):
|
|
return False
|
|
|
|
if exact and (len(shown) != len(rest)):
|
|
return False
|
|
|
|
for a, b in zip(shown, rest):
|
|
if a != b:
|
|
return False
|
|
|
|
return True
|
|
|
|
def get_showing_tags(self, layer):
|
|
"""
|
|
Returns the set of tags being shown on `layer`.
|
|
"""
|
|
|
|
return { t for l, t in self.shown if l == layer }
|
|
|
|
def get_hidden_tags(self, layer):
|
|
"""
|
|
Returns the set of tags on layer that have attributes,
|
|
but aren't being shown.
|
|
"""
|
|
|
|
return { t for l, t in self.attributes if l == layer if (l, t) not in self.shown }
|
|
|
|
def predict_scene(self, layer):
|
|
"""
|
|
Predicts the scene statement being called on layer.
|
|
"""
|
|
|
|
if layer is None:
|
|
layer = 'master'
|
|
|
|
for l, t in self.attributes.keys():
|
|
if l == layer:
|
|
del self.attributes[l, t]
|
|
|
|
self.shown = set((l, t) for l, t in self.shown if l != layer)
|
|
|
|
def predict_show(self, layer, name, show=True):
|
|
"""
|
|
Predicts name being shown on layer.
|
|
|
|
`show`
|
|
If True, the image will be flagged as being shown to the user. If
|
|
False, only the attributes will be updated.
|
|
"""
|
|
|
|
tag = name[0]
|
|
rest = name[1:]
|
|
|
|
if layer is None:
|
|
layer = renpy.config.tag_layer.get(tag, "master")
|
|
|
|
self.attributes[layer, tag] = rest
|
|
|
|
if show:
|
|
self.shown.add((layer, tag))
|
|
|
|
def predict_hide(self, layer, name):
|
|
tag = name[0]
|
|
|
|
if layer is None:
|
|
layer = renpy.config.tag_layer.get(tag, "master")
|
|
|
|
if (layer, tag) in self.attributes:
|
|
del self.attributes[layer, tag]
|
|
|
|
self.shown.discard((layer, tag))
|
|
|
|
def apply_attributes(self, layer, tag, name, wanted=[], remove=[]):
|
|
"""
|
|
Given a layer, tag, and an image name (with attributes),
|
|
returns the canonical name of an image, if one exists. Raises
|
|
an exception if it's ambiguious, and returns None if an image
|
|
with that name couldn't be found.
|
|
"""
|
|
|
|
if layer is None:
|
|
layer = renpy.config.tag_layer.get(tag, "master")
|
|
|
|
# If the name matches one that exactly exists, return it.
|
|
if (name in images) and not (wanted or remove):
|
|
ca = getattr(images[name], "_choose_attributes", None)
|
|
|
|
if ca is None:
|
|
return name
|
|
|
|
nametag = name[0]
|
|
|
|
# The set of attributes a matching image may have.
|
|
optional = set(wanted) | set(self.attributes.get((layer, tag), [ ]))
|
|
|
|
# The set of attributes a matching image must have/not have.
|
|
# Evaluated in order.
|
|
required = set()
|
|
for i in name[1:]:
|
|
if i[0] == "-":
|
|
optional.discard(i[1:])
|
|
required.discard(i[1:])
|
|
else:
|
|
required.add(i)
|
|
|
|
for i in remove:
|
|
optional.discard(i)
|
|
|
|
return self.choose_image(nametag, required, optional, name)
|
|
|
|
def choose_image(self, tag, required, optional, exception_name):
|
|
|
|
# The longest length of an image that matches.
|
|
max_len = -1
|
|
|
|
# The list of matching images.
|
|
matches = None
|
|
|
|
for attrs, d in image_attributes[tag].items():
|
|
|
|
ca = getattr(d, "_choose_attributes", None)
|
|
|
|
if ca:
|
|
ca_required = { i for i in required if i not in attrs }
|
|
ca_optional = { i for i in optional if i not in attrs }
|
|
newattrs = ca(tag, ca_required, ca_optional)
|
|
|
|
if newattrs is None:
|
|
continue
|
|
|
|
attrs = attrs + newattrs
|
|
|
|
num_required = 0
|
|
|
|
for i in attrs:
|
|
if i in required:
|
|
num_required += 1
|
|
continue
|
|
|
|
elif i not in optional:
|
|
break
|
|
|
|
else:
|
|
|
|
# We don't have any not-found attributes. But we might not
|
|
# have all of the attributes.
|
|
|
|
if num_required != len(required):
|
|
continue
|
|
|
|
len_attrs = len(set(attrs))
|
|
|
|
if len_attrs < max_len:
|
|
continue
|
|
|
|
if len_attrs > max_len:
|
|
max_len = len_attrs
|
|
matches = [ ]
|
|
|
|
matches.append((tag, ) + attrs)
|
|
|
|
if matches is None:
|
|
return None
|
|
|
|
if len(matches) == 1:
|
|
return matches[0]
|
|
|
|
if exception_name:
|
|
raise Exception("Showing '" + " ".join(exception_name) + "' is ambiguous, possible images include: " + ", ".join(" ".join(i) for i in matches))
|
|
else:
|
|
return None
|
|
|
|
|
|
renpy.display.core.ImagePredictInfo = ShownImageInfo
|
|
|
|
|
|
# Functions that have moved from this module to other modules,
|
|
# that live here for the purpose of backward-compatibility.
|
|
Image = renpy.display.im.image
|
|
Solid = renpy.display.imagelike.Solid
|
|
Frame = renpy.display.imagelike.Frame
|
|
ImageButton = renpy.display.behavior.ImageButton
|