ttyengine 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ttyengine/__init__.py +45 -0
- ttyengine/ansi.py +201 -0
- ttyengine/ascii_styler.py +473 -0
- ttyengine/components.py +4718 -0
- ttyengine/compositor.py +780 -0
- ttyengine/errors.py +181 -0
- ttyengine/figlet-LICENSE.txt +27 -0
- ttyengine/figlet.py +430 -0
- ttyengine/fonts.zip +0 -0
- ttyengine/image_reader.py +813 -0
- ttyengine/input_handler.py +814 -0
- ttyengine/launcher.py +822 -0
- ttyengine/region.py +180 -0
- ttyengine/scene.py +614 -0
- ttyengine/sound.py +1439 -0
- ttyengine/terminal.py +283 -0
- ttyengine-0.1.0.dist-info/METADATA +903 -0
- ttyengine-0.1.0.dist-info/RECORD +21 -0
- ttyengine-0.1.0.dist-info/WHEEL +5 -0
- ttyengine-0.1.0.dist-info/licenses/LICENSE +373 -0
- ttyengine-0.1.0.dist-info/top_level.txt +1 -0
ttyengine/scene.py
ADDED
|
@@ -0,0 +1,614 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
def _argslen(cb):
|
|
3
|
+
params = inspect.signature(cb).parameters
|
|
4
|
+
l = len(params) - (1 if hasattr(cb, '__self__') else 0)
|
|
5
|
+
return l
|
|
6
|
+
def get_components(module):
|
|
7
|
+
return [
|
|
8
|
+
cls for _, cls in inspect.getmembers(module, inspect.isclass)
|
|
9
|
+
if cls.__module__ == module.__name__
|
|
10
|
+
]
|
|
11
|
+
def wrap_property(component_attr: str, prop: property) -> property:
|
|
12
|
+
def fget(self):
|
|
13
|
+
return prop.fget(getattr(self, component_attr))
|
|
14
|
+
def fset(self, value):
|
|
15
|
+
prop.fset(getattr(self, component_attr), value)
|
|
16
|
+
return property(fget, fset if prop.fset else None)
|
|
17
|
+
|
|
18
|
+
from . import ansi
|
|
19
|
+
from . import region as _regions
|
|
20
|
+
from . import components as _components
|
|
21
|
+
from .ascii_styler import Styler
|
|
22
|
+
from .compositor import Compositor
|
|
23
|
+
_comps = []
|
|
24
|
+
_attrs = {}
|
|
25
|
+
_temps = []
|
|
26
|
+
_allcomps = {}
|
|
27
|
+
_defls = []
|
|
28
|
+
for _cls in get_components(_components):
|
|
29
|
+
defls = hasattr(_cls, '_owned')
|
|
30
|
+
reqrs = hasattr(_cls, '_required')
|
|
31
|
+
defl = hasattr(_cls, '_default')
|
|
32
|
+
_allcomps[_cls] = _argslen(_cls)
|
|
33
|
+
if defls or reqrs or defl:
|
|
34
|
+
_comps.append(_cls)
|
|
35
|
+
if defls:
|
|
36
|
+
for _attr in _cls._owned:
|
|
37
|
+
_attrs[_attr] = _cls
|
|
38
|
+
if defl:
|
|
39
|
+
if _cls._default: _defls.append(_cls)
|
|
40
|
+
else:
|
|
41
|
+
_temps.append(_cls)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _findcomp(name):
|
|
45
|
+
for comp, params in _allcomps.items():
|
|
46
|
+
if comp.__name__ == name:
|
|
47
|
+
return comp, params
|
|
48
|
+
raise KeyError(f'Unknown component: {name!r}')
|
|
49
|
+
|
|
50
|
+
def _scopes():
|
|
51
|
+
found = {}
|
|
52
|
+
for cls in _comps:
|
|
53
|
+
names = getattr(cls, '_settings', None)
|
|
54
|
+
owned = getattr(cls, '_owned', None)
|
|
55
|
+
if not names or not owned:
|
|
56
|
+
continue
|
|
57
|
+
aliases = getattr(cls, '_aliases', {})
|
|
58
|
+
mapping = {name: aliases.get(name, name) for name in names}
|
|
59
|
+
if all(flat in owned for flat in mapping.values()):
|
|
60
|
+
found[cls.__name__] = (cls, mapping)
|
|
61
|
+
return found
|
|
62
|
+
_scoped = _scopes()
|
|
63
|
+
|
|
64
|
+
def _expand(kwargs):
|
|
65
|
+
if not any(name in kwargs for name in _scoped):
|
|
66
|
+
return kwargs, ()
|
|
67
|
+
flat = {}
|
|
68
|
+
scoped = {}
|
|
69
|
+
forced = []
|
|
70
|
+
for key, value in kwargs.items():
|
|
71
|
+
scope = _scoped.get(key)
|
|
72
|
+
if scope is None or not (isinstance(value, dict) or (value is True and key not in scope[0]._owned)):
|
|
73
|
+
flat[key] = value
|
|
74
|
+
continue
|
|
75
|
+
cls, mapping = scope
|
|
76
|
+
forced.append(cls)
|
|
77
|
+
if isinstance(value, dict):
|
|
78
|
+
for name, val in value.items():
|
|
79
|
+
if name not in mapping:
|
|
80
|
+
raise TypeError(f'unknown {key} setting: {name}')
|
|
81
|
+
scoped[mapping[name]] = val
|
|
82
|
+
flat.update(scoped)
|
|
83
|
+
return flat, forced
|
|
84
|
+
|
|
85
|
+
def _comname(c):
|
|
86
|
+
if isinstance(c, str):
|
|
87
|
+
return c
|
|
88
|
+
return c.__name__ if inspect.isclass(c) else type(c).__name__
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class obj:
|
|
92
|
+
def __init__(self, scene):
|
|
93
|
+
self.scene = scene
|
|
94
|
+
self._ondiscardcb = []
|
|
95
|
+
self._ghosts = []
|
|
96
|
+
|
|
97
|
+
def __getitem__(self, key):
|
|
98
|
+
return getattr(getattr(self, _attrs[key].__name__), key)
|
|
99
|
+
|
|
100
|
+
def __setitem__(self, key, value):
|
|
101
|
+
setattr(getattr(self, _attrs[key].__name__), key, value)
|
|
102
|
+
|
|
103
|
+
def add(self, com, **kwargs):
|
|
104
|
+
kwargs, _ = _expand(kwargs)
|
|
105
|
+
if isinstance(com, str):
|
|
106
|
+
comp, params = _findcomp(com)
|
|
107
|
+
else:
|
|
108
|
+
comp = com
|
|
109
|
+
params = _argslen(com)
|
|
110
|
+
if params > 1:
|
|
111
|
+
compins = comp(self, **kwargs)
|
|
112
|
+
elif params > 0:
|
|
113
|
+
compins = comp(self)
|
|
114
|
+
else:
|
|
115
|
+
compins = comp()
|
|
116
|
+
ow = getattr(compins, '_owned', None)
|
|
117
|
+
ch = getattr(compins, '_changed', None)
|
|
118
|
+
|
|
119
|
+
for k, v in kwargs.items():
|
|
120
|
+
if ow and k in ow:
|
|
121
|
+
val = getattr(compins, k, None)
|
|
122
|
+
if val == v:
|
|
123
|
+
if not ch:
|
|
124
|
+
compins._changed = {k: v}
|
|
125
|
+
ch = compins._changed
|
|
126
|
+
else:
|
|
127
|
+
ch.update({k: v})
|
|
128
|
+
elif k not in _attrs:
|
|
129
|
+
setattr(self, k, v)
|
|
130
|
+
|
|
131
|
+
compinsattr = comp.__name__
|
|
132
|
+
setattr(self, compinsattr, compins)
|
|
133
|
+
return compins
|
|
134
|
+
|
|
135
|
+
def set(self, comn, **kwargs):
|
|
136
|
+
kwargs, _ = _expand(kwargs)
|
|
137
|
+
com = comn if isinstance(comn, (tuple, list)) else [comn]
|
|
138
|
+
for c in com:
|
|
139
|
+
if isinstance(c, str):
|
|
140
|
+
c, _ = _findcomp(c)
|
|
141
|
+
if inspect.isclass(c):
|
|
142
|
+
if not hasattr(self, c.__name__):
|
|
143
|
+
for rcom in getattr(c, '_required', ()):
|
|
144
|
+
if not hasattr(self, rcom.__name__):
|
|
145
|
+
self.add(rcom, **kwargs)
|
|
146
|
+
self.add(c, **kwargs)
|
|
147
|
+
c = getattr(self, c.__name__)
|
|
148
|
+
ow = getattr(c, '_owned', None)
|
|
149
|
+
ch = getattr(c, '_changed', None)
|
|
150
|
+
for k, v in kwargs.items():
|
|
151
|
+
if (ow and k in ow) or (not ow and hasattr(c, k)):
|
|
152
|
+
setattr(c, k, v)
|
|
153
|
+
if ow and k in ow:
|
|
154
|
+
if not ch:
|
|
155
|
+
c._changed = {k: v}
|
|
156
|
+
ch = c._changed
|
|
157
|
+
else:
|
|
158
|
+
ch.update({k: v})
|
|
159
|
+
elif k not in _attrs:
|
|
160
|
+
setattr(self, k, v)
|
|
161
|
+
|
|
162
|
+
def kwargs(self, coms=None):
|
|
163
|
+
if coms is None:
|
|
164
|
+
names = None
|
|
165
|
+
else:
|
|
166
|
+
if isinstance(coms, str) or inspect.isclass(coms):
|
|
167
|
+
coms = [coms]
|
|
168
|
+
names = {_comname(c) for c in coms}
|
|
169
|
+
result = {}
|
|
170
|
+
for at, cl in _attrs.items():
|
|
171
|
+
if names is not None and cl.__name__ not in names:
|
|
172
|
+
continue
|
|
173
|
+
com = getattr(self, cl.__name__, None)
|
|
174
|
+
changed = getattr(com, '_changed', None)
|
|
175
|
+
if at in result or not com or not changed: continue
|
|
176
|
+
result.update({k: getattr(com, k) for k in changed})
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
def clone(self, coms=None, **overrides):
|
|
180
|
+
kwargs = self.kwargs(coms)
|
|
181
|
+
kwargs.update(overrides)
|
|
182
|
+
return self.scene.obj().create(**kwargs)
|
|
183
|
+
|
|
184
|
+
def _ensure(self, comp, kwargs, processed):
|
|
185
|
+
if comp in processed:
|
|
186
|
+
return
|
|
187
|
+
if not hasattr(self, comp.__name__):
|
|
188
|
+
for rcom in getattr(comp, '_required', ()):
|
|
189
|
+
if rcom not in processed:
|
|
190
|
+
if not hasattr(self, rcom.__name__):
|
|
191
|
+
self.add(rcom, **kwargs)
|
|
192
|
+
else:
|
|
193
|
+
self.set(rcom, **kwargs)
|
|
194
|
+
processed.add(rcom)
|
|
195
|
+
self.add(comp, **kwargs)
|
|
196
|
+
else:
|
|
197
|
+
self.set(comp, **kwargs)
|
|
198
|
+
processed.add(comp)
|
|
199
|
+
|
|
200
|
+
def create(self, text=None, x=None, y=None, **kwargs):
|
|
201
|
+
kwargs.update({k: v for k, v in {'text': text, 'x': x, 'y': y}.items() if v is not None})
|
|
202
|
+
kwargs, forced = _expand(kwargs)
|
|
203
|
+
processed = set()
|
|
204
|
+
|
|
205
|
+
for com in _defls:
|
|
206
|
+
self._ensure(com, kwargs, processed)
|
|
207
|
+
|
|
208
|
+
for k in list(kwargs.keys()):
|
|
209
|
+
comp = _attrs.get(k)
|
|
210
|
+
if comp:
|
|
211
|
+
self._ensure(comp, kwargs, processed)
|
|
212
|
+
else:
|
|
213
|
+
setattr(self, k, kwargs[k])
|
|
214
|
+
|
|
215
|
+
for comp in forced:
|
|
216
|
+
self._ensure(comp, kwargs, processed)
|
|
217
|
+
|
|
218
|
+
if self not in self.scene.objs:
|
|
219
|
+
self.scene.objs.append(self)
|
|
220
|
+
return self
|
|
221
|
+
|
|
222
|
+
def ondiscard(self, callback):
|
|
223
|
+
self._ondiscardcb.append(callback)
|
|
224
|
+
return callback
|
|
225
|
+
|
|
226
|
+
def offdiscard(self, callback):
|
|
227
|
+
self._ondiscardcb = [cb for cb in self._ondiscardcb if cb is not callback]
|
|
228
|
+
|
|
229
|
+
def discard(self, clean=True):
|
|
230
|
+
for dscb in list(self._ondiscardcb):
|
|
231
|
+
dscb()
|
|
232
|
+
self._ondiscardcb = []
|
|
233
|
+
owned = []
|
|
234
|
+
for co in _allcomps:
|
|
235
|
+
own = getattr(self, co.__name__, None)
|
|
236
|
+
if own is None: continue
|
|
237
|
+
owned.append((co.__name__, own))
|
|
238
|
+
for _, com in owned:
|
|
239
|
+
if hasattr(com, 'on_discard'):
|
|
240
|
+
com.on_discard()
|
|
241
|
+
if self in self.scene.objs:
|
|
242
|
+
self.scene.objs.remove(self)
|
|
243
|
+
for name, _ in owned:
|
|
244
|
+
delattr(self, name)
|
|
245
|
+
self.scene._getlast(True)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class Scene:
|
|
249
|
+
def __init__(self):
|
|
250
|
+
self.objs = []
|
|
251
|
+
self.dinfo = []
|
|
252
|
+
self.pending = []
|
|
253
|
+
self._maxframerate = 60
|
|
254
|
+
self.framerate = 60
|
|
255
|
+
self._oncamcb = {}
|
|
256
|
+
self._ondimcb = {}
|
|
257
|
+
self._ondimoffsetcb = {}
|
|
258
|
+
self._firing = False
|
|
259
|
+
self.termx = 120
|
|
260
|
+
self.termy = 30
|
|
261
|
+
self._dimx = self.termx
|
|
262
|
+
self._dimy = self.termy
|
|
263
|
+
self._dimxoffset = 0
|
|
264
|
+
self._dimyoffset = 0
|
|
265
|
+
self._last = {}
|
|
266
|
+
self._getlast(True)
|
|
267
|
+
self.solids = []
|
|
268
|
+
self.gravity = (0, 0)
|
|
269
|
+
self.drag = 1
|
|
270
|
+
self.precision = 0.5
|
|
271
|
+
self.aspect = 0.5
|
|
272
|
+
self.lights = []
|
|
273
|
+
self.ambient = 1.0
|
|
274
|
+
self._camx = 0
|
|
275
|
+
self._camy = 0
|
|
276
|
+
self._camrotation = 0.0
|
|
277
|
+
self._bgcolor = (0, 0, 0)
|
|
278
|
+
self._fade = None
|
|
279
|
+
self._ticks = 0
|
|
280
|
+
self._compositor = None
|
|
281
|
+
self.fgcolor = (204, 204, 204)
|
|
282
|
+
self.styler = Styler(self)
|
|
283
|
+
self.recomp = ansi.SEQUENCE
|
|
284
|
+
self._lastz = 0
|
|
285
|
+
self._firstz = 0
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def dimx(self):
|
|
289
|
+
return self._dimx
|
|
290
|
+
@dimx.setter
|
|
291
|
+
def dimx(self, value):
|
|
292
|
+
old = self._dimx
|
|
293
|
+
self._dimx = self.iround(value)
|
|
294
|
+
if self._dimx != old:
|
|
295
|
+
self._fire(self._ondimcb, 'x', old)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def dimy(self):
|
|
299
|
+
return self._dimy
|
|
300
|
+
@dimy.setter
|
|
301
|
+
def dimy(self, value):
|
|
302
|
+
old = self._dimy
|
|
303
|
+
self._dimy = self.iround(value)
|
|
304
|
+
if self._dimy != old:
|
|
305
|
+
self._fire(self._ondimcb, 'y', old)
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def dimxoffset(self):
|
|
309
|
+
return self._dimxoffset
|
|
310
|
+
@dimxoffset.setter
|
|
311
|
+
def dimxoffset(self, value):
|
|
312
|
+
old = self._dimxoffset
|
|
313
|
+
self._dimxoffset = self.iround(value)
|
|
314
|
+
if self._dimxoffset != old:
|
|
315
|
+
self._fire(self._ondimoffsetcb, 'x', old)
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def dimyoffset(self):
|
|
319
|
+
return self._dimyoffset
|
|
320
|
+
@dimyoffset.setter
|
|
321
|
+
def dimyoffset(self, value):
|
|
322
|
+
old = self._dimyoffset
|
|
323
|
+
self._dimyoffset = self.iround(value)
|
|
324
|
+
if self._dimyoffset != old:
|
|
325
|
+
self._fire(self._ondimoffsetcb, 'y', old)
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def canvas(self):
|
|
329
|
+
return (self.termx, self.termy)
|
|
330
|
+
@canvas.setter
|
|
331
|
+
def canvas(self, value):
|
|
332
|
+
self.termx, self.termy = (max(0, self.iround(v)) for v in value)
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def view(self):
|
|
336
|
+
return _regions.Region(self._dimxoffset, -self._dimyoffset, self._dimx, self._dimy)
|
|
337
|
+
@view.setter
|
|
338
|
+
def view(self, value):
|
|
339
|
+
x, y, w, h = _regions.resolve(value, self.termx, self.termy)
|
|
340
|
+
self.dimxoffset = x
|
|
341
|
+
self.dimyoffset = -y
|
|
342
|
+
self.dimx = w
|
|
343
|
+
self.dimy = h
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def framerate(self):
|
|
347
|
+
return self._framerate
|
|
348
|
+
@framerate.setter
|
|
349
|
+
def framerate(self, value):
|
|
350
|
+
if not value > 0:
|
|
351
|
+
raise ValueError('framerate must be positive')
|
|
352
|
+
self._framerate = min(self._maxframerate, value)
|
|
353
|
+
|
|
354
|
+
@property
|
|
355
|
+
def maxframerate(self):
|
|
356
|
+
return self._maxframerate
|
|
357
|
+
@maxframerate.setter
|
|
358
|
+
def maxframerate(self, value):
|
|
359
|
+
if not value > 0:
|
|
360
|
+
raise ValueError('maxframerate must be positive')
|
|
361
|
+
self._maxframerate = value
|
|
362
|
+
self._framerate = min(value, self._framerate)
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def bgcolor(self):
|
|
366
|
+
return self._bgcolor
|
|
367
|
+
@bgcolor.setter
|
|
368
|
+
def bgcolor(self, value):
|
|
369
|
+
self._bgcolor = self._color(value)
|
|
370
|
+
self._getlast(True)
|
|
371
|
+
|
|
372
|
+
@property
|
|
373
|
+
def fading(self):
|
|
374
|
+
return self._fade is not None
|
|
375
|
+
|
|
376
|
+
def _color(self, value):
|
|
377
|
+
if isinstance(value, (int, float)):
|
|
378
|
+
value = int(value)
|
|
379
|
+
return (value, value, value)
|
|
380
|
+
color = Styler.rgb(tuple(value) if isinstance(value, list) else value)
|
|
381
|
+
if color is None:
|
|
382
|
+
raise ValueError('unknown color: %r' % (value,))
|
|
383
|
+
return color
|
|
384
|
+
|
|
385
|
+
def fade(self, color, time=1, delay=0, start=None, callback=None, ease='linear'):
|
|
386
|
+
if time < 0 or delay < 0:
|
|
387
|
+
raise ValueError('time and delay must not be negative')
|
|
388
|
+
curve = _components._easing(ease)
|
|
389
|
+
target = self._color(color)
|
|
390
|
+
origin = self._color(self._bgcolor if start is None else start)
|
|
391
|
+
if start is not None:
|
|
392
|
+
self._fade = None
|
|
393
|
+
self.bgcolor = origin
|
|
394
|
+
self._fade = {'from': origin, 'to': target, 'time': time, 'delay': delay,
|
|
395
|
+
'elapsed': 0.0, 'cb': callback, 'ease': curve}
|
|
396
|
+
if time <= 0 and delay <= 0:
|
|
397
|
+
self._finishfade()
|
|
398
|
+
return self
|
|
399
|
+
|
|
400
|
+
def unfade(self, finish=False):
|
|
401
|
+
f = self._fade
|
|
402
|
+
self._fade = None
|
|
403
|
+
if f is not None and finish:
|
|
404
|
+
self.bgcolor = f['to']
|
|
405
|
+
return self
|
|
406
|
+
|
|
407
|
+
def _finishfade(self):
|
|
408
|
+
f = self._fade
|
|
409
|
+
self._fade = None
|
|
410
|
+
self.bgcolor = f['to']
|
|
411
|
+
cb = f['cb']
|
|
412
|
+
if callable(cb):
|
|
413
|
+
if _argslen(cb):
|
|
414
|
+
cb(self)
|
|
415
|
+
else:
|
|
416
|
+
cb()
|
|
417
|
+
|
|
418
|
+
def tick(self, physics=True):
|
|
419
|
+
if physics:
|
|
420
|
+
for o in list(self.objs):
|
|
421
|
+
phy = getattr(o, 'physics', None)
|
|
422
|
+
if phy is not None and phy.dynamic:
|
|
423
|
+
phy.physics()
|
|
424
|
+
self._ticks += 1
|
|
425
|
+
self._tickfade()
|
|
426
|
+
for o in list(self.objs):
|
|
427
|
+
tf = getattr(o, 'transform', None)
|
|
428
|
+
if tf is not None:
|
|
429
|
+
tf._tick()
|
|
430
|
+
for o in list(self.objs):
|
|
431
|
+
jnt = getattr(o, 'joint', None)
|
|
432
|
+
if physics and jnt is not None:
|
|
433
|
+
jnt.step()
|
|
434
|
+
lgt = getattr(o, 'light', None)
|
|
435
|
+
if lgt is not None:
|
|
436
|
+
lgt.step()
|
|
437
|
+
|
|
438
|
+
@property
|
|
439
|
+
def compositor(self):
|
|
440
|
+
if self._compositor is None:
|
|
441
|
+
self._compositor = Compositor()
|
|
442
|
+
return self._compositor
|
|
443
|
+
|
|
444
|
+
def render(self, region=None, outer=None, autobg=True, full=False):
|
|
445
|
+
comp = self.compositor
|
|
446
|
+
if full:
|
|
447
|
+
comp.invalidate()
|
|
448
|
+
x, y, w, h = region if region is not None else (0, 0, self.termx, self.termy)
|
|
449
|
+
return comp.compose([self], [self], (x, y, w, h), (x + w, y + h), outer, autobg)
|
|
450
|
+
|
|
451
|
+
def _tickfade(self):
|
|
452
|
+
f = self._fade
|
|
453
|
+
if f is None:
|
|
454
|
+
return
|
|
455
|
+
dt = 1 / self.framerate
|
|
456
|
+
if f['delay'] > 0:
|
|
457
|
+
f['delay'] -= dt
|
|
458
|
+
if f['delay'] > 0:
|
|
459
|
+
return
|
|
460
|
+
dt = -f['delay']
|
|
461
|
+
f['delay'] = 0
|
|
462
|
+
f['elapsed'] += dt
|
|
463
|
+
if f['elapsed'] >= f['time']:
|
|
464
|
+
self._finishfade()
|
|
465
|
+
return
|
|
466
|
+
p = f['ease'](f['elapsed'] / f['time'])
|
|
467
|
+
self.bgcolor = tuple(max(0, min(255, int(a + (b - a) * p))) for a, b in zip(f['from'], f['to']))
|
|
468
|
+
|
|
469
|
+
@property
|
|
470
|
+
def camx(self):
|
|
471
|
+
return self._camx
|
|
472
|
+
@camx.setter
|
|
473
|
+
def camx(self, value):
|
|
474
|
+
old = self._camx
|
|
475
|
+
self._camx = value
|
|
476
|
+
if value != old:
|
|
477
|
+
self._fire(self._oncamcb, 'x', old)
|
|
478
|
+
|
|
479
|
+
@property
|
|
480
|
+
def camy(self):
|
|
481
|
+
return self._camy
|
|
482
|
+
@camy.setter
|
|
483
|
+
def camy(self, value):
|
|
484
|
+
old = self._camy
|
|
485
|
+
self._camy = value
|
|
486
|
+
if value != old:
|
|
487
|
+
self._fire(self._oncamcb, 'y', old)
|
|
488
|
+
|
|
489
|
+
@property
|
|
490
|
+
def camrotation(self):
|
|
491
|
+
return self._camrotation
|
|
492
|
+
@camrotation.setter
|
|
493
|
+
def camrotation(self, value):
|
|
494
|
+
old = self._camrotation
|
|
495
|
+
self._camrotation = float(value) % 360.0
|
|
496
|
+
if self._camrotation != old:
|
|
497
|
+
self._fire(self._oncamcb, 'rotation', old)
|
|
498
|
+
|
|
499
|
+
def focus(self, target):
|
|
500
|
+
t = getattr(target, 'transform', target)
|
|
501
|
+
pivot = getattr(t, 'pivot', None)
|
|
502
|
+
if pivot is None:
|
|
503
|
+
return target[0], target[1]
|
|
504
|
+
return pivot()
|
|
505
|
+
|
|
506
|
+
def follow(self, target=None, y=None, smooth=1, offset=(0, 0)):
|
|
507
|
+
if target is None or isinstance(target, (int, float)):
|
|
508
|
+
px, py = target, y
|
|
509
|
+
else:
|
|
510
|
+
px, py = self.focus(target)
|
|
511
|
+
dx = dy = 0
|
|
512
|
+
if px is not None:
|
|
513
|
+
goal = px + offset[0] - self._dimx / 2
|
|
514
|
+
dx = goal - self._camx
|
|
515
|
+
dx = dx / smooth if smooth > 1 and abs(dx) > 0.01 else dx
|
|
516
|
+
if py is not None:
|
|
517
|
+
goal = py + offset[1] + self._dimy / 2
|
|
518
|
+
dy = goal - self._camy
|
|
519
|
+
dy = dy / smooth if smooth > 1 and abs(dy) > 0.01 else dy
|
|
520
|
+
if dx:
|
|
521
|
+
self.camx = self._camx + dx
|
|
522
|
+
if dy:
|
|
523
|
+
self.camy = self._camy + dy
|
|
524
|
+
return dx, dy
|
|
525
|
+
|
|
526
|
+
def _fire(self, callbacks, att='', old=None):
|
|
527
|
+
internal = self._firing
|
|
528
|
+
self._firing = True
|
|
529
|
+
try:
|
|
530
|
+
for cb, params in list(callbacks.items()):
|
|
531
|
+
if params > 2:
|
|
532
|
+
cb(att, old, internal)
|
|
533
|
+
elif params > 1:
|
|
534
|
+
cb(att, old)
|
|
535
|
+
elif params > 0:
|
|
536
|
+
cb(att)
|
|
537
|
+
else:
|
|
538
|
+
cb()
|
|
539
|
+
finally:
|
|
540
|
+
self._firing = internal
|
|
541
|
+
|
|
542
|
+
def _register_once(self, cb, store, once):
|
|
543
|
+
match_att = once if isinstance(once, str) else None
|
|
544
|
+
l = _argslen(cb)
|
|
545
|
+
fired = [False]
|
|
546
|
+
orig = cb
|
|
547
|
+
def wrapped(att=None, old=None, internal=False):
|
|
548
|
+
if fired[0]: return
|
|
549
|
+
if match_att is not None and att != match_att: return
|
|
550
|
+
fired[0] = True
|
|
551
|
+
if wrapped in store:
|
|
552
|
+
del store[wrapped]
|
|
553
|
+
if l > 2: orig(att, old, internal)
|
|
554
|
+
elif l > 1: orig(att, old)
|
|
555
|
+
elif l > 0: orig(att)
|
|
556
|
+
else: orig()
|
|
557
|
+
wrapped.__wrapped__ = orig
|
|
558
|
+
store[wrapped] = 3
|
|
559
|
+
return wrapped
|
|
560
|
+
|
|
561
|
+
def _register(self, callback, store, once):
|
|
562
|
+
if once:
|
|
563
|
+
self._register_once(callback, store, once)
|
|
564
|
+
else:
|
|
565
|
+
store[callback] = _argslen(callback)
|
|
566
|
+
return callback
|
|
567
|
+
|
|
568
|
+
@staticmethod
|
|
569
|
+
def _unregister(store, callback):
|
|
570
|
+
return {cb: pars for cb, pars in store.items()
|
|
571
|
+
if cb is not callback and getattr(cb, '__wrapped__', None) is not callback}
|
|
572
|
+
|
|
573
|
+
def oncam(self, callback, once=False):
|
|
574
|
+
return self._register(callback, self._oncamcb, once)
|
|
575
|
+
|
|
576
|
+
def offcam(self, callback):
|
|
577
|
+
self._oncamcb = self._unregister(self._oncamcb, callback)
|
|
578
|
+
|
|
579
|
+
def ondim(self, callback, once=False):
|
|
580
|
+
return self._register(callback, self._ondimcb, once)
|
|
581
|
+
|
|
582
|
+
def offdim(self, callback):
|
|
583
|
+
self._ondimcb = self._unregister(self._ondimcb, callback)
|
|
584
|
+
|
|
585
|
+
def ondimoffset(self, callback, once=False):
|
|
586
|
+
return self._register(callback, self._ondimoffsetcb, once)
|
|
587
|
+
|
|
588
|
+
def offdimoffset(self, callback):
|
|
589
|
+
self._ondimoffsetcb = self._unregister(self._ondimoffsetcb, callback)
|
|
590
|
+
|
|
591
|
+
def iround(self, x):
|
|
592
|
+
return int((x + 0.5) // 1)
|
|
593
|
+
|
|
594
|
+
def obj(self):
|
|
595
|
+
return obj(self)
|
|
596
|
+
|
|
597
|
+
def flush(self):
|
|
598
|
+
for func in self.pending:
|
|
599
|
+
func()
|
|
600
|
+
self.pending = []
|
|
601
|
+
|
|
602
|
+
def _getlast(self, none=False):
|
|
603
|
+
for k,v in vars(self).items():
|
|
604
|
+
if k.startswith('term') or k.startswith('_dim'):
|
|
605
|
+
self._last[k] = v if not none else None
|
|
606
|
+
|
|
607
|
+
def checkres(self, reg=True):
|
|
608
|
+
changed = False
|
|
609
|
+
for k,v in self._last.items():
|
|
610
|
+
val = getattr(self, k)
|
|
611
|
+
if val != v:
|
|
612
|
+
changed = True
|
|
613
|
+
if reg: self._last[k] = val
|
|
614
|
+
return changed
|