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 22# Functions that make the user's life easier. 23 24from __future__ import division, absolute_import, with_statement, print_function, unicode_literals 25from renpy.compat import * 26 27 28import renpy.display 29import renpy.styledata 30import contextlib 31import time 32 33Color = renpy.color.Color 34color = renpy.color.Color 35 36 37def lookup_displayable_prefix(d): 38 """ 39 Given `d`, a string given a displayable, returns the displayale it 40 corresponds to or None if it it does not correspond to one. 41 """ 42 43 prefix, colon, arg = d.partition(":") 44 45 if not colon: 46 return None 47 48 fn = renpy.config.displayable_prefix.get(prefix, None) 49 if fn is None: 50 return None 51 52 return displayable(fn(arg)) 53 54 55def displayable_or_none(d, scope=None, dynamic=True): 56 57 if isinstance(d, renpy.display.core.Displayable): 58 return d 59 60 if d is None: 61 return d 62 63 if isinstance(d, basestring): 64 if not d: 65 raise Exception("An empty string cannot be used as a displayable.") 66 elif ("[" in d) and renpy.config.dynamic_images and dynamic: 67 return renpy.display.image.DynamicImage(d, scope=scope) 68 69 rv = lookup_displayable_prefix(d) 70 71 if rv is not None: 72 return rv 73 elif d[0] == '#': 74 return renpy.store.Solid(d) 75 elif "." in d: 76 return renpy.store.Image(d) 77 else: 78 return renpy.store.ImageReference(tuple(d.split())) 79 80 if isinstance(d, Color): 81 return renpy.store.Solid(d) 82 83 if isinstance(d, list): 84 return renpy.display.image.DynamicImage(d, scope=scope) 85 86 # We assume the user knows what he's doing in this case. 87 if hasattr(d, '_duplicate'): 88 return d 89 90 if d is True or d is False: 91 return d 92 93 raise Exception("Not a displayable: %r" % (d,)) 94 95 96def displayable(d, scope=None): 97 """ 98 :doc: udd_utility 99 :name: renpy.displayable 100 101 This takes `d`, which may be a displayable object or a string. If it's 102 a string, it converts that string into a displayable using the usual 103 rules. 104 """ 105 106 if isinstance(d, renpy.display.core.Displayable): 107 return d 108 109 if isinstance(d, basestring): 110 if not d: 111 raise Exception("An empty string cannot be used as a displayable.") 112 elif ("[" in d) and renpy.config.dynamic_images: 113 return renpy.display.image.DynamicImage(d, scope=scope) 114 115 rv = lookup_displayable_prefix(d) 116 117 if rv is not None: 118 return rv 119 elif d[0] == '#': 120 return renpy.store.Solid(d) 121 elif "." in d: 122 return renpy.store.Image(d) 123 else: 124 return renpy.store.ImageReference(tuple(d.split())) 125 126 if isinstance(d, Color): 127 return renpy.store.Solid(d) 128 129 if isinstance(d, list): 130 return renpy.display.image.DynamicImage(d, scope=scope) 131 132 # We assume the user knows what he's doing in this case. 133 if hasattr(d, '_duplicate'): 134 return d 135 136 if d is True or d is False: 137 return d 138 139 raise Exception("Not a displayable: %r" % (d,)) 140 141 142def dynamic_image(d, scope=None, prefix=None, search=None): 143 """ 144 Substitutes a scope into `d`, then returns a displayable. 145 146 If `prefix` is given, and a prefix has been given a prefix search is 147 performed until a file is found. (Only a file can be used in this case.) 148 """ 149 150 if not isinstance(d, list): 151 d = [ d ] 152 153 def find(name): 154 155 if renpy.exports.image_exists(name): 156 return True 157 158 if renpy.loader.loadable(name): 159 return True 160 161 if lookup_displayable_prefix(name): 162 return True 163 164 if (len(d) == 1) and (renpy.config.missing_image_callback is not None): 165 if renpy.config.missing_image_callback(name): 166 return True 167 168 for i in d: 169 170 if not isinstance(i, basestring): 171 continue 172 173 if (prefix is not None) and ("[prefix_" in i): 174 175 if scope: 176 scope = dict(scope) 177 else: 178 scope = { } 179 180 for p in renpy.styledata.stylesets.prefix_search[prefix]: # @UndefinedVariable 181 scope["prefix_"] = p 182 183 rv = renpy.substitutions.substitute(i, scope=scope, force=True, translate=False)[0] 184 185 if find(rv): 186 return displayable_or_none(rv) 187 188 if search is not None: 189 search.append(rv) 190 191 else: 192 193 rv = renpy.substitutions.substitute(i, scope=scope, force=True, translate=False)[0] 194 195 if find(rv): 196 return displayable_or_none(rv) 197 198 if search is not None: 199 search.append(rv) 200 201 else: 202 203 rv = d[-1] 204 205 if find(rv): 206 return displayable_or_none(rv, dynamic=False) 207 208 return None 209 210 211def predict(d): 212 d = renpy.easy.displayable_or_none(d) 213 214 if d is not None: 215 renpy.display.predict.displayable(d) 216 217 218@contextlib.contextmanager 219def timed(name): 220 start = time.time() 221 yield 222 print("{0}: {1:.2f} ms".format(name, (time.time() - start) * 1000.0)) 223 224 225def split_properties(properties, *prefixes): 226 """ 227 :doc: other 228 229 Splits up `properties` into multiple dictionaries, one per `prefix`. This 230 function checks each key in properties against each prefix, in turn. 231 When a prefix matches, the prefix is stripped from the key, and the 232 resulting key is mapped to the value in the corresponding dictionary. 233 234 If no prefix matches, an exception is thrown. (The empty string, "", 235 can be used as the last prefix to create a catch-all dictionary.) 236 237 For example, this splits properties beginning with text from 238 those that do not:: 239 240 text_properties, button_properties = renpy.split_properties(properties, "text_", "") 241 """ 242 243 rv = [ ] 244 245 for _i in prefixes: 246 rv.append({}) 247 248 if not properties: 249 return rv 250 251 prefix_d = list(zip(prefixes, rv)) 252 253 for k, v in properties.items(): 254 for prefix, d in prefix_d: 255 if k.startswith(prefix): 256 d[k[len(prefix):]] = v 257 break 258 else: 259 raise Exception("Property {} begins with an unknown prefix.".format(k)) 260 261 return rv 262