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/region.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from collections import namedtuple
|
|
3
|
+
|
|
4
|
+
Region = namedtuple('Region', 'x y width height')
|
|
5
|
+
|
|
6
|
+
_PERCENT = re.compile(r'^\s*(-?\d+(?:\.\d+)?)\s*%\s*$')
|
|
7
|
+
_NUMBER = re.compile(r'^\s*-?\d+(?:\.\d+)?\s*$')
|
|
8
|
+
_ALIGNS = ('start', 'center', 'end')
|
|
9
|
+
_KEYS = {
|
|
10
|
+
'x': 'x', 'left': 'x', 'y': 'y', 'top': 'y',
|
|
11
|
+
'width': 'width', 'w': 'width', 'height': 'height', 'h': 'height',
|
|
12
|
+
'right': 'right', 'bottom': 'bottom',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _number(value):
|
|
17
|
+
if isinstance(value, bool):
|
|
18
|
+
raise ValueError('region values must be numbers, percentages or alignments, not %r' % (value,))
|
|
19
|
+
if isinstance(value, (int, float)):
|
|
20
|
+
if value != value or value in (float('inf'), float('-inf')):
|
|
21
|
+
raise ValueError('region values must be finite, got %r' % (value,))
|
|
22
|
+
return int(round(value))
|
|
23
|
+
if isinstance(value, str) and _NUMBER.match(value):
|
|
24
|
+
return int(round(float(value)))
|
|
25
|
+
return None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _clean(value, name, aligned):
|
|
29
|
+
if value is None:
|
|
30
|
+
return None
|
|
31
|
+
if isinstance(value, str):
|
|
32
|
+
text = value.strip().lower()
|
|
33
|
+
if _PERCENT.match(text):
|
|
34
|
+
return text.replace(' ', '')
|
|
35
|
+
if aligned and text in _ALIGNS + ('cursor',):
|
|
36
|
+
return text
|
|
37
|
+
number = _number(value)
|
|
38
|
+
if number is None:
|
|
39
|
+
raise ValueError('invalid region %s: %r' % (name, value))
|
|
40
|
+
return number
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def parse(spec):
|
|
44
|
+
if spec is None:
|
|
45
|
+
return Region(0, 0, None, None)
|
|
46
|
+
if isinstance(spec, dict):
|
|
47
|
+
unknown = [k for k in spec if k not in _KEYS]
|
|
48
|
+
if unknown:
|
|
49
|
+
raise ValueError('unknown region key(s): %s' % ', '.join(map(str, unknown)))
|
|
50
|
+
merged = {}
|
|
51
|
+
for key, value in spec.items():
|
|
52
|
+
target = _KEYS[key]
|
|
53
|
+
if target in merged and merged[target] != value:
|
|
54
|
+
raise ValueError('region key %r given twice' % (key,))
|
|
55
|
+
merged[target] = value
|
|
56
|
+
x, y = merged.get('x'), merged.get('y')
|
|
57
|
+
width, height = merged.get('width'), merged.get('height')
|
|
58
|
+
right, bottom = merged.get('right'), merged.get('bottom')
|
|
59
|
+
x, width = _anchor(x, width, right, 'right', 'width')
|
|
60
|
+
y, height = _anchor(y, height, bottom, 'bottom', 'height')
|
|
61
|
+
else:
|
|
62
|
+
try:
|
|
63
|
+
x, y, width, height = spec
|
|
64
|
+
except (TypeError, ValueError):
|
|
65
|
+
raise ValueError('a region is None, a dict or (x, y, width, height), got %r' % (spec,)) from None
|
|
66
|
+
return Region(
|
|
67
|
+
_clean(x, 'x', True) if x is not None else 0,
|
|
68
|
+
_clean(y, 'y', True) if y is not None else 0,
|
|
69
|
+
_clean(width, 'width', False),
|
|
70
|
+
_clean(height, 'height', False),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _anchor(pos, size, inset, inset_name, size_name):
|
|
75
|
+
if inset is None:
|
|
76
|
+
return pos, size
|
|
77
|
+
inset = _number(inset)
|
|
78
|
+
if inset is None or inset < 0:
|
|
79
|
+
raise ValueError('%s must be a non-negative number of cells' % inset_name)
|
|
80
|
+
if size is None:
|
|
81
|
+
return pos, -inset
|
|
82
|
+
size_cells = _number(size)
|
|
83
|
+
if pos is not None or size_cells is None:
|
|
84
|
+
raise ValueError('%s with %s needs a numeric %s and no explicit start' % (inset_name, size_name, size_name))
|
|
85
|
+
return -(inset + size_cells), size_cells
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _fraction(text, total):
|
|
89
|
+
return int(round(total * float(_PERCENT.match(text).group(1)) / 100.0))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _axis(pos, size, total, cursor):
|
|
93
|
+
if pos in ('center', 'end'):
|
|
94
|
+
if size is None:
|
|
95
|
+
length = total
|
|
96
|
+
elif isinstance(size, str):
|
|
97
|
+
length = _fraction(size, total)
|
|
98
|
+
elif size < 0:
|
|
99
|
+
length = total + size
|
|
100
|
+
else:
|
|
101
|
+
length = size
|
|
102
|
+
length = max(0, min(length, total))
|
|
103
|
+
start = (total - length) // 2 if pos == 'center' else total - length
|
|
104
|
+
return start, length
|
|
105
|
+
if pos == 'start' or pos is None:
|
|
106
|
+
start = 0
|
|
107
|
+
elif pos == 'cursor':
|
|
108
|
+
start = cursor
|
|
109
|
+
elif isinstance(pos, str):
|
|
110
|
+
start = _fraction(pos, total)
|
|
111
|
+
if start < 0:
|
|
112
|
+
start += total
|
|
113
|
+
else:
|
|
114
|
+
start = pos + total if pos < 0 else pos
|
|
115
|
+
start = max(0, min(start, total))
|
|
116
|
+
room = total - start
|
|
117
|
+
if size is None:
|
|
118
|
+
length = room
|
|
119
|
+
elif isinstance(size, str):
|
|
120
|
+
length = _fraction(size, total)
|
|
121
|
+
elif size < 0:
|
|
122
|
+
length = room + size
|
|
123
|
+
else:
|
|
124
|
+
length = size
|
|
125
|
+
return start, max(0, min(length, room))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def resolve(spec, width, height, cursor=0):
|
|
129
|
+
x, y, w, h = parse(spec)
|
|
130
|
+
rx, rw = _axis(x, w, max(0, int(width)), 0)
|
|
131
|
+
ry, rh = _axis(y, h, max(0, int(height)), cursor)
|
|
132
|
+
return Region(rx, ry, rw, rh)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def is_default(spec):
|
|
136
|
+
return parse(spec) == Region(0, 0, None, None)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def uses_cursor(spec):
|
|
140
|
+
return parse(spec).y == 'cursor'
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def full():
|
|
144
|
+
return Region(0, 0, None, None)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def line(row=0):
|
|
148
|
+
return Region(0, row, None, 1)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def column(col=0):
|
|
152
|
+
return Region(col, 0, 1, None)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def top(rows=1):
|
|
156
|
+
return Region(0, 0, None, rows)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def bottom(rows=1):
|
|
160
|
+
return Region(0, -rows, None, rows)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def left(columns=1):
|
|
164
|
+
return Region(0, 0, columns, None)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def right(columns=1):
|
|
168
|
+
return Region(-columns, 0, columns, None)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def box(x, y, width, height):
|
|
172
|
+
return Region(x, y, width, height)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def centered(width, height):
|
|
176
|
+
return Region('center', 'center', width, height)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def inline(rows=1):
|
|
180
|
+
return Region(0, 'cursor', None, rows)
|