urwid 2.6.0.post0__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.
Potentially problematic release.
This version of urwid might be problematic. Click here for more details.
- urwid/__init__.py +333 -0
- urwid/canvas.py +1413 -0
- urwid/command_map.py +137 -0
- urwid/container.py +59 -0
- urwid/decoration.py +65 -0
- urwid/display/__init__.py +97 -0
- urwid/display/_posix_raw_display.py +413 -0
- urwid/display/_raw_display_base.py +914 -0
- urwid/display/_web.css +12 -0
- urwid/display/_web.js +462 -0
- urwid/display/_win32.py +171 -0
- urwid/display/_win32_raw_display.py +269 -0
- urwid/display/common.py +1219 -0
- urwid/display/curses.py +690 -0
- urwid/display/escape.py +624 -0
- urwid/display/html_fragment.py +251 -0
- urwid/display/lcd.py +518 -0
- urwid/display/raw.py +37 -0
- urwid/display/web.py +636 -0
- urwid/event_loop/__init__.py +55 -0
- urwid/event_loop/abstract_loop.py +175 -0
- urwid/event_loop/asyncio_loop.py +231 -0
- urwid/event_loop/glib_loop.py +294 -0
- urwid/event_loop/main_loop.py +721 -0
- urwid/event_loop/select_loop.py +230 -0
- urwid/event_loop/tornado_loop.py +206 -0
- urwid/event_loop/trio_loop.py +302 -0
- urwid/event_loop/twisted_loop.py +269 -0
- urwid/event_loop/zmq_loop.py +275 -0
- urwid/font.py +695 -0
- urwid/graphics.py +96 -0
- urwid/highlight.css +19 -0
- urwid/listbox.py +1899 -0
- urwid/monitored_list.py +522 -0
- urwid/numedit.py +376 -0
- urwid/signals.py +330 -0
- urwid/split_repr.py +130 -0
- urwid/str_util.py +358 -0
- urwid/text_layout.py +632 -0
- urwid/treetools.py +515 -0
- urwid/util.py +557 -0
- urwid/version.py +16 -0
- urwid/vterm.py +1806 -0
- urwid/widget/__init__.py +181 -0
- urwid/widget/attr_map.py +161 -0
- urwid/widget/attr_wrap.py +140 -0
- urwid/widget/bar_graph.py +649 -0
- urwid/widget/big_text.py +77 -0
- urwid/widget/box_adapter.py +126 -0
- urwid/widget/columns.py +1145 -0
- urwid/widget/constants.py +574 -0
- urwid/widget/container.py +227 -0
- urwid/widget/divider.py +110 -0
- urwid/widget/edit.py +718 -0
- urwid/widget/filler.py +403 -0
- urwid/widget/frame.py +539 -0
- urwid/widget/grid_flow.py +539 -0
- urwid/widget/line_box.py +194 -0
- urwid/widget/overlay.py +829 -0
- urwid/widget/padding.py +597 -0
- urwid/widget/pile.py +971 -0
- urwid/widget/popup.py +170 -0
- urwid/widget/progress_bar.py +141 -0
- urwid/widget/scrollable.py +597 -0
- urwid/widget/solid_fill.py +44 -0
- urwid/widget/text.py +354 -0
- urwid/widget/widget.py +852 -0
- urwid/widget/widget_decoration.py +166 -0
- urwid/widget/wimp.py +792 -0
- urwid/wimp.py +23 -0
- urwid-2.6.0.post0.dist-info/COPYING +504 -0
- urwid-2.6.0.post0.dist-info/METADATA +332 -0
- urwid-2.6.0.post0.dist-info/RECORD +75 -0
- urwid-2.6.0.post0.dist-info/WHEEL +5 -0
- urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
urwid/widget/overlay.py
ADDED
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
import warnings
|
|
5
|
+
|
|
6
|
+
from urwid.canvas import CanvasOverlay, CompositeCanvas
|
|
7
|
+
|
|
8
|
+
from .constants import (
|
|
9
|
+
RELATIVE_100,
|
|
10
|
+
Align,
|
|
11
|
+
Sizing,
|
|
12
|
+
VAlign,
|
|
13
|
+
WHSettings,
|
|
14
|
+
WrapMode,
|
|
15
|
+
normalize_align,
|
|
16
|
+
normalize_height,
|
|
17
|
+
normalize_valign,
|
|
18
|
+
normalize_width,
|
|
19
|
+
simplify_align,
|
|
20
|
+
simplify_height,
|
|
21
|
+
simplify_valign,
|
|
22
|
+
simplify_width,
|
|
23
|
+
)
|
|
24
|
+
from .container import WidgetContainerListContentsMixin, WidgetContainerMixin
|
|
25
|
+
from .filler import calculate_top_bottom_filler
|
|
26
|
+
from .padding import calculate_left_right_padding
|
|
27
|
+
from .widget import Widget, WidgetError, WidgetWarning
|
|
28
|
+
|
|
29
|
+
if typing.TYPE_CHECKING:
|
|
30
|
+
from collections.abc import Iterator
|
|
31
|
+
|
|
32
|
+
from typing_extensions import Literal
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class OverlayError(WidgetError):
|
|
36
|
+
"""Overlay specific errors."""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class OverlayWarning(WidgetWarning):
|
|
40
|
+
"""Overlay specific warnings."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _check_widget_subclass(widget: Widget) -> None:
|
|
44
|
+
if not isinstance(widget, Widget):
|
|
45
|
+
obj_class_path = f"{widget.__class__.__module__}.{widget.__class__.__name__}"
|
|
46
|
+
warnings.warn(
|
|
47
|
+
f"{obj_class_path} is not subclass of Widget",
|
|
48
|
+
DeprecationWarning,
|
|
49
|
+
stacklevel=3,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class OverlayOptions(typing.NamedTuple):
|
|
54
|
+
align: Align
|
|
55
|
+
align_amount: int | None
|
|
56
|
+
width_type: WHSettings
|
|
57
|
+
width_amount: int | None
|
|
58
|
+
min_width: int | None
|
|
59
|
+
left: int
|
|
60
|
+
right: int
|
|
61
|
+
valign_type: VAlign
|
|
62
|
+
valign_amount: int | None
|
|
63
|
+
height_type: WHSettings
|
|
64
|
+
height_amount: int | None
|
|
65
|
+
min_height: int | None
|
|
66
|
+
top: int
|
|
67
|
+
bottom: int
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Overlay(Widget, WidgetContainerMixin, WidgetContainerListContentsMixin):
|
|
71
|
+
"""
|
|
72
|
+
Overlay contains two box widgets and renders one on top of the other
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
_selectable = True
|
|
76
|
+
|
|
77
|
+
_DEFAULT_BOTTOM_OPTIONS = OverlayOptions(
|
|
78
|
+
align=Align.LEFT,
|
|
79
|
+
align_amount=None,
|
|
80
|
+
width_type=WHSettings.RELATIVE,
|
|
81
|
+
width_amount=100,
|
|
82
|
+
min_width=None,
|
|
83
|
+
left=0,
|
|
84
|
+
right=0,
|
|
85
|
+
valign_type=VAlign.TOP,
|
|
86
|
+
valign_amount=None,
|
|
87
|
+
height_type=WHSettings.RELATIVE,
|
|
88
|
+
height_amount=100,
|
|
89
|
+
min_height=None,
|
|
90
|
+
top=0,
|
|
91
|
+
bottom=0,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
top_w: Widget,
|
|
97
|
+
bottom_w: Widget,
|
|
98
|
+
align: (
|
|
99
|
+
Literal["left", "center", "right"]
|
|
100
|
+
| Align
|
|
101
|
+
| tuple[Literal["relative", "fixed left", "fixed right", WHSettings.RELATIVE], int]
|
|
102
|
+
),
|
|
103
|
+
width: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
|
|
104
|
+
valign: (
|
|
105
|
+
Literal["top", "middle", "bottom"]
|
|
106
|
+
| VAlign
|
|
107
|
+
| tuple[Literal["relative", "fixed top", "fixed bottom", WHSettings.RELATIVE], int]
|
|
108
|
+
),
|
|
109
|
+
height: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
|
|
110
|
+
min_width: int | None = None,
|
|
111
|
+
min_height: int | None = None,
|
|
112
|
+
left: int = 0,
|
|
113
|
+
right: int = 0,
|
|
114
|
+
top: int = 0,
|
|
115
|
+
bottom: int = 0,
|
|
116
|
+
) -> None:
|
|
117
|
+
"""
|
|
118
|
+
:param top_w: a flow, box or fixed widget to overlay "on top"
|
|
119
|
+
:type top_w: Widget
|
|
120
|
+
:param bottom_w: a box widget to appear "below" previous widget
|
|
121
|
+
:type bottom_w: Widget
|
|
122
|
+
:param align: alignment, one of ``'left'``, ``'center'``, ``'right'`` or
|
|
123
|
+
(``'relative'``, *percentage* 0=left 100=right)
|
|
124
|
+
:type align: str
|
|
125
|
+
:param width: width type, one of:
|
|
126
|
+
|
|
127
|
+
``'pack'``
|
|
128
|
+
if *top_w* is a fixed widget
|
|
129
|
+
*given width*
|
|
130
|
+
integer number of columns wide
|
|
131
|
+
(``'relative'``, *percentage of total width*)
|
|
132
|
+
make *top_w* width related to container width
|
|
133
|
+
|
|
134
|
+
:param valign: alignment mode, one of ``'top'``, ``'middle'``, ``'bottom'`` or
|
|
135
|
+
(``'relative'``, *percentage* 0=top 100=bottom)
|
|
136
|
+
:param height: one of:
|
|
137
|
+
|
|
138
|
+
``'pack'``
|
|
139
|
+
if *top_w* is a flow or fixed widget
|
|
140
|
+
*given height*
|
|
141
|
+
integer number of rows high
|
|
142
|
+
(``'relative'``, *percentage of total height*)
|
|
143
|
+
make *top_w* height related to container height
|
|
144
|
+
:param min_width: the minimum number of columns for *top_w* when width is not fixed
|
|
145
|
+
:type min_width: int
|
|
146
|
+
:param min_height: minimum number of rows for *top_w* when height is not fixed
|
|
147
|
+
:type min_height: int
|
|
148
|
+
:param left: a fixed number of columns to add on the left
|
|
149
|
+
:type left: int
|
|
150
|
+
:param right: a fixed number of columns to add on the right
|
|
151
|
+
:type right: int
|
|
152
|
+
:param top: a fixed number of rows to add on the top
|
|
153
|
+
:type top: int
|
|
154
|
+
:param bottom: a fixed number of rows to add on the bottom
|
|
155
|
+
:type bottom: int
|
|
156
|
+
|
|
157
|
+
Overlay widgets behave similarly to :class:`Padding` and :class:`Filler`
|
|
158
|
+
widgets when determining the size and position of *top_w*. *bottom_w* is
|
|
159
|
+
always rendered the full size available "below" *top_w*.
|
|
160
|
+
"""
|
|
161
|
+
super().__init__()
|
|
162
|
+
|
|
163
|
+
self.top_w = top_w
|
|
164
|
+
self.bottom_w = bottom_w
|
|
165
|
+
|
|
166
|
+
self.set_overlay_parameters(align, width, valign, height, min_width, min_height, left, right, top, bottom)
|
|
167
|
+
|
|
168
|
+
_check_widget_subclass(top_w)
|
|
169
|
+
_check_widget_subclass(bottom_w)
|
|
170
|
+
|
|
171
|
+
def sizing(self) -> frozenset[Sizing]:
|
|
172
|
+
"""Actual widget sizing.
|
|
173
|
+
|
|
174
|
+
:returns: Sizing information depends on the top widget sizing and sizing parameters.
|
|
175
|
+
:rtype: frozenset[Sizing]
|
|
176
|
+
|
|
177
|
+
Rules:
|
|
178
|
+
* BOX sizing is always supported provided by the bottom widget
|
|
179
|
+
* FLOW sizing is supported if top widget has:
|
|
180
|
+
* * PACK height type and FLOW supported by the TOP widget
|
|
181
|
+
* * BOX supported by TOP widget AND height amount AND height type GIVEN of min_height
|
|
182
|
+
* FIXED sizing is supported if top widget has:
|
|
183
|
+
* * PACK width type and FIXED supported by the TOP widget
|
|
184
|
+
* * width amount and GIVEN width or min_width AND:
|
|
185
|
+
* * * FLOW supported by the TOP widget AND PACK height type
|
|
186
|
+
* * * BOX supported by the TOP widget AND height_amount and GIVEN height or min height
|
|
187
|
+
"""
|
|
188
|
+
sizing = {Sizing.BOX}
|
|
189
|
+
top_sizing = self.top_w.sizing()
|
|
190
|
+
if self.width_type == WHSettings.PACK:
|
|
191
|
+
if Sizing.FIXED in top_sizing:
|
|
192
|
+
sizing.add(Sizing.FIXED)
|
|
193
|
+
else:
|
|
194
|
+
warnings.warn(
|
|
195
|
+
f"Top widget {self.top_w} should support sizing {Sizing.FIXED.upper()} "
|
|
196
|
+
f"for width type {WHSettings.PACK.upper()}",
|
|
197
|
+
OverlayWarning,
|
|
198
|
+
stacklevel=3,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
elif self.height_type == WHSettings.PACK:
|
|
202
|
+
if Sizing.FLOW in top_sizing:
|
|
203
|
+
sizing.add(Sizing.FLOW)
|
|
204
|
+
|
|
205
|
+
if self.width_amount and (self.width_type == WHSettings.GIVEN or self.min_width):
|
|
206
|
+
sizing.add(Sizing.FIXED)
|
|
207
|
+
else:
|
|
208
|
+
warnings.warn(
|
|
209
|
+
f"Top widget {self.top_w} should support sizing {Sizing.FLOW.upper()} "
|
|
210
|
+
f"for height type {self.height_type.upper()}",
|
|
211
|
+
OverlayWarning,
|
|
212
|
+
stacklevel=3,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
elif self.height_amount and (self.height_type == WHSettings.GIVEN or self.min_height):
|
|
216
|
+
if Sizing.BOX in top_sizing:
|
|
217
|
+
sizing.add(Sizing.FLOW)
|
|
218
|
+
if self.width_amount and (self.width_type == WHSettings.GIVEN or self.min_width):
|
|
219
|
+
sizing.add(Sizing.FIXED)
|
|
220
|
+
else:
|
|
221
|
+
warnings.warn(
|
|
222
|
+
f"Top widget {self.top_w} should support sizing {Sizing.BOX.upper()} "
|
|
223
|
+
f"for height type {self.height_type.upper()}",
|
|
224
|
+
OverlayWarning,
|
|
225
|
+
stacklevel=3,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return frozenset(sizing)
|
|
229
|
+
|
|
230
|
+
def pack(self, size: tuple[()] | tuple[int] | tuple[int, int] = (), focus: bool = False) -> tuple[int, int]:
|
|
231
|
+
if size:
|
|
232
|
+
return super().pack(size, focus)
|
|
233
|
+
|
|
234
|
+
extra_cols = (self.left or 0) + (self.right or 0)
|
|
235
|
+
extra_rows = (self.top or 0) + (self.bottom or 0)
|
|
236
|
+
|
|
237
|
+
if self.width_type == WHSettings.PACK:
|
|
238
|
+
cols, rows = self.top_w.pack((), focus)
|
|
239
|
+
return cols + extra_cols, rows + extra_rows
|
|
240
|
+
|
|
241
|
+
if not self.width_amount:
|
|
242
|
+
raise OverlayError(
|
|
243
|
+
f"Requested FIXED render for {self.top_w} with "
|
|
244
|
+
f"width_type={self.width_type.upper()}, "
|
|
245
|
+
f"width_amount={self.width_amount!r}, "
|
|
246
|
+
f"height_type={self.height_type.upper()}, "
|
|
247
|
+
f"height_amount={self.height_amount!r}"
|
|
248
|
+
f"min_width={self.min_width!r}, "
|
|
249
|
+
f"min_height={self.min_height!r}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
if self.width_type == WHSettings.GIVEN:
|
|
253
|
+
w_cols = self.width_amount
|
|
254
|
+
cols = w_cols + extra_cols
|
|
255
|
+
elif self.width_type == WHSettings.RELATIVE and self.min_width:
|
|
256
|
+
w_cols = self.min_width
|
|
257
|
+
cols = int(w_cols * 100 / self.width_amount + 0.5)
|
|
258
|
+
else:
|
|
259
|
+
raise OverlayError(
|
|
260
|
+
f"Requested FIXED render for {self.top_w} with "
|
|
261
|
+
f"width_type={self.width_type.upper()}, "
|
|
262
|
+
f"width_amount={self.width_amount!r}, "
|
|
263
|
+
f"height_type={self.height_type.upper()}, "
|
|
264
|
+
f"height_amount={self.height_amount!r}"
|
|
265
|
+
f"min_width={self.min_width!r}, "
|
|
266
|
+
f"min_height={self.min_height!r}"
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
if self.height_type == WHSettings.PACK:
|
|
270
|
+
return cols, self.top_w.rows((w_cols,), focus) + extra_rows
|
|
271
|
+
|
|
272
|
+
if not self.height_amount:
|
|
273
|
+
raise OverlayError(
|
|
274
|
+
f"Requested FIXED render for {self.top_w} with "
|
|
275
|
+
f"width_type={self.width_type.upper()}, "
|
|
276
|
+
f"width_amount={self.width_amount!r}, "
|
|
277
|
+
f"height_type={self.height_type.upper()}, "
|
|
278
|
+
f"height_amount={self.height_amount!r}"
|
|
279
|
+
f"min_width={self.min_width!r}, "
|
|
280
|
+
f"min_height={self.min_height!r}"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if self.height_type == WHSettings.GIVEN:
|
|
284
|
+
return cols, self.height_amount + extra_rows
|
|
285
|
+
|
|
286
|
+
if self.height_type == WHSettings.RELATIVE and self.min_height:
|
|
287
|
+
return cols, int(self.min_height * 100 / self.height_amount + 0.5)
|
|
288
|
+
|
|
289
|
+
raise OverlayError(
|
|
290
|
+
f"Requested FIXED render for {self.top_w} with "
|
|
291
|
+
f"width_type={self.width_type.upper()}, "
|
|
292
|
+
f"width_amount={self.width_amount!r}, "
|
|
293
|
+
f"height_type={self.height_type.upper()}, "
|
|
294
|
+
f"height_amount={self.height_amount!r}"
|
|
295
|
+
f"min_width={self.min_width!r}, "
|
|
296
|
+
f"min_height={self.min_height!r}"
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def rows(self, size: tuple[int], focus: bool = False) -> int:
|
|
300
|
+
"""Widget rows amount for FLOW sizing."""
|
|
301
|
+
extra_height = (self.top or 0) + (self.bottom or 0)
|
|
302
|
+
if self.height_type == WHSettings.GIVEN:
|
|
303
|
+
return self.height_amount + extra_height
|
|
304
|
+
if self.height_type == WHSettings.RELATIVE and self.min_height:
|
|
305
|
+
return int(self.min_height * 100 / self.height_amount + 0.5)
|
|
306
|
+
|
|
307
|
+
if self.height_type == WHSettings.PACK:
|
|
308
|
+
extra_height = (self.top or 0) + (self.bottom or 0)
|
|
309
|
+
if self.width_type == WHSettings.GIVEN and self.width_amount:
|
|
310
|
+
return self.top_w.rows((self.width_amount,), focus) + extra_height
|
|
311
|
+
if self.width_type == WHSettings.RELATIVE:
|
|
312
|
+
width = max(int(size[0] * self.width_amount / 100 + 0.5), (self.min_width or 0))
|
|
313
|
+
return self.top_w.rows((width,), focus) + extra_height
|
|
314
|
+
|
|
315
|
+
raise OverlayError(
|
|
316
|
+
f"Requested rows for {self.top_w} with size {size!r}"
|
|
317
|
+
f"width_type={self.width_type.upper()}, "
|
|
318
|
+
f"width_amount={self.width_amount!r}, "
|
|
319
|
+
f"height_type={self.height_type.upper()}, "
|
|
320
|
+
f"height_amount={self.height_amount!r}"
|
|
321
|
+
f"min_width={self.min_width!r}, "
|
|
322
|
+
f"min_height={self.min_height!r}"
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
def _repr_words(self) -> list[str]:
|
|
326
|
+
return [*super()._repr_words(), f"top={self.top_w!r}", f"bottom={self.bottom_w!r}"]
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def options(
|
|
330
|
+
align_type: Literal["left", "center", "right", "relative"] | Align,
|
|
331
|
+
align_amount: int | None,
|
|
332
|
+
width_type: Literal["clip", "pack", "relative", "given"] | WHSettings,
|
|
333
|
+
width_amount: int | None,
|
|
334
|
+
valign_type: Literal["top", "middle", "bottom", "relative"] | VAlign,
|
|
335
|
+
valign_amount: int | None,
|
|
336
|
+
height_type: Literal["flow", "pack", "relative", "given"] | WHSettings,
|
|
337
|
+
height_amount: int | None,
|
|
338
|
+
min_width: int | None = None,
|
|
339
|
+
min_height: int | None = None,
|
|
340
|
+
left: int = 0,
|
|
341
|
+
right: int = 0,
|
|
342
|
+
top: int = 0,
|
|
343
|
+
bottom: int = 0,
|
|
344
|
+
) -> OverlayOptions:
|
|
345
|
+
"""
|
|
346
|
+
Return a new options tuple for use in this Overlay's .contents mapping.
|
|
347
|
+
|
|
348
|
+
This is the common container API to create options for replacing the
|
|
349
|
+
top widget of this Overlay. It is provided for completeness
|
|
350
|
+
but is not necessarily the easiest way to change the overlay parameters.
|
|
351
|
+
See also :meth:`.set_overlay_parameters`
|
|
352
|
+
"""
|
|
353
|
+
|
|
354
|
+
return OverlayOptions(
|
|
355
|
+
Align(align_type),
|
|
356
|
+
align_amount,
|
|
357
|
+
WHSettings(width_type),
|
|
358
|
+
width_amount,
|
|
359
|
+
min_width,
|
|
360
|
+
left,
|
|
361
|
+
right,
|
|
362
|
+
VAlign(valign_type),
|
|
363
|
+
valign_amount,
|
|
364
|
+
WHSettings(height_type),
|
|
365
|
+
height_amount,
|
|
366
|
+
min_height,
|
|
367
|
+
top,
|
|
368
|
+
bottom,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
def set_overlay_parameters(
|
|
372
|
+
self,
|
|
373
|
+
align: (
|
|
374
|
+
Literal["left", "center", "right"]
|
|
375
|
+
| Align
|
|
376
|
+
| tuple[Literal["relative", "fixed left", "fixed right", WHSettings.RELATIVE], int]
|
|
377
|
+
),
|
|
378
|
+
width: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
|
|
379
|
+
valign: (
|
|
380
|
+
Literal["top", "middle", "bottom"]
|
|
381
|
+
| VAlign
|
|
382
|
+
| tuple[Literal["relative", "fixed top", "fixed bottom", WHSettings.RELATIVE], int]
|
|
383
|
+
),
|
|
384
|
+
height: Literal["pack", WHSettings.PACK] | int | tuple[Literal["relative", WHSettings.RELATIVE], int] | None,
|
|
385
|
+
min_width: int | None = None,
|
|
386
|
+
min_height: int | None = None,
|
|
387
|
+
left: int = 0,
|
|
388
|
+
right: int = 0,
|
|
389
|
+
top: int = 0,
|
|
390
|
+
bottom: int = 0,
|
|
391
|
+
) -> None:
|
|
392
|
+
"""
|
|
393
|
+
Adjust the overlay size and position parameters.
|
|
394
|
+
|
|
395
|
+
See :class:`__init__() <Overlay>` for a description of the parameters.
|
|
396
|
+
"""
|
|
397
|
+
|
|
398
|
+
# convert obsolete parameters 'fixed ...':
|
|
399
|
+
if isinstance(align, tuple):
|
|
400
|
+
if align[0] == "fixed left":
|
|
401
|
+
left = align[1]
|
|
402
|
+
normalized_align = Align.LEFT
|
|
403
|
+
elif align[0] == "fixed right":
|
|
404
|
+
right = align[1]
|
|
405
|
+
normalized_align = Align.RIGHT
|
|
406
|
+
else:
|
|
407
|
+
normalized_align = align
|
|
408
|
+
else:
|
|
409
|
+
normalized_align = Align(align)
|
|
410
|
+
|
|
411
|
+
if isinstance(width, tuple):
|
|
412
|
+
if width[0] == "fixed left":
|
|
413
|
+
left = width[1]
|
|
414
|
+
width = RELATIVE_100
|
|
415
|
+
elif width[0] == "fixed right":
|
|
416
|
+
right = width[1]
|
|
417
|
+
width = RELATIVE_100
|
|
418
|
+
|
|
419
|
+
if isinstance(valign, tuple):
|
|
420
|
+
if valign[0] == "fixed top":
|
|
421
|
+
top = valign[1]
|
|
422
|
+
normalized_valign = VAlign.TOP
|
|
423
|
+
elif valign[0] == "fixed bottom":
|
|
424
|
+
bottom = valign[1]
|
|
425
|
+
normalized_valign = VAlign.BOTTOM
|
|
426
|
+
else:
|
|
427
|
+
normalized_valign = valign
|
|
428
|
+
|
|
429
|
+
elif not isinstance(valign, (VAlign, str)):
|
|
430
|
+
raise OverlayError(f"invalid valign: {valign!r}")
|
|
431
|
+
|
|
432
|
+
else:
|
|
433
|
+
normalized_valign = VAlign(valign)
|
|
434
|
+
|
|
435
|
+
if isinstance(height, tuple):
|
|
436
|
+
if height[0] == "fixed bottom":
|
|
437
|
+
bottom = height[1]
|
|
438
|
+
height = RELATIVE_100
|
|
439
|
+
elif height[0] == "fixed top":
|
|
440
|
+
top = height[1]
|
|
441
|
+
height = RELATIVE_100
|
|
442
|
+
|
|
443
|
+
if width is None: # more obsolete values accepted
|
|
444
|
+
width = WHSettings.PACK
|
|
445
|
+
if height is None:
|
|
446
|
+
height = WHSettings.PACK
|
|
447
|
+
|
|
448
|
+
align_type, align_amount = normalize_align(normalized_align, OverlayError)
|
|
449
|
+
width_type, width_amount = normalize_width(width, OverlayError)
|
|
450
|
+
valign_type, valign_amount = normalize_valign(normalized_valign, OverlayError)
|
|
451
|
+
height_type, height_amount = normalize_height(height, OverlayError)
|
|
452
|
+
|
|
453
|
+
if height_type in {WHSettings.GIVEN, WHSettings.PACK}:
|
|
454
|
+
min_height = None
|
|
455
|
+
|
|
456
|
+
# use container API to set the parameters
|
|
457
|
+
self.contents[1] = (
|
|
458
|
+
self.top_w,
|
|
459
|
+
self.options(
|
|
460
|
+
align_type,
|
|
461
|
+
align_amount,
|
|
462
|
+
width_type,
|
|
463
|
+
width_amount,
|
|
464
|
+
valign_type,
|
|
465
|
+
valign_amount,
|
|
466
|
+
height_type,
|
|
467
|
+
height_amount,
|
|
468
|
+
min_width,
|
|
469
|
+
min_height,
|
|
470
|
+
left,
|
|
471
|
+
right,
|
|
472
|
+
top,
|
|
473
|
+
bottom,
|
|
474
|
+
),
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
def selectable(self) -> bool:
|
|
478
|
+
"""Return selectable from top_w."""
|
|
479
|
+
return self.top_w.selectable()
|
|
480
|
+
|
|
481
|
+
def keypress(self, size: tuple[()] | tuple[int] | tuple[int, int], key: str) -> str | None:
|
|
482
|
+
"""Pass keypress to top_w."""
|
|
483
|
+
real_size = self.pack(size, True)
|
|
484
|
+
return self.top_w.keypress(self.top_w_size(real_size, *self.calculate_padding_filler(real_size, True)), key)
|
|
485
|
+
|
|
486
|
+
@property
|
|
487
|
+
def focus(self) -> Widget:
|
|
488
|
+
"""
|
|
489
|
+
Read-only property returning the child widget in focus for
|
|
490
|
+
container widgets. This default implementation
|
|
491
|
+
always returns ``None``, indicating that this widget has no children.
|
|
492
|
+
"""
|
|
493
|
+
return self.top_w
|
|
494
|
+
|
|
495
|
+
def _get_focus(self) -> Widget:
|
|
496
|
+
warnings.warn(
|
|
497
|
+
f"method `{self.__class__.__name__}._get_focus` is deprecated, "
|
|
498
|
+
f"please use `{self.__class__.__name__}.focus` property",
|
|
499
|
+
DeprecationWarning,
|
|
500
|
+
stacklevel=3,
|
|
501
|
+
)
|
|
502
|
+
return self.top_w
|
|
503
|
+
|
|
504
|
+
@property
|
|
505
|
+
def focus_position(self) -> Literal[1]:
|
|
506
|
+
"""
|
|
507
|
+
Return the top widget position (currently always 1).
|
|
508
|
+
"""
|
|
509
|
+
return 1
|
|
510
|
+
|
|
511
|
+
@focus_position.setter
|
|
512
|
+
def focus_position(self, position: int) -> None:
|
|
513
|
+
"""
|
|
514
|
+
Set the widget in focus. Currently only position 0 is accepted.
|
|
515
|
+
|
|
516
|
+
position -- index of child widget to be made focus
|
|
517
|
+
"""
|
|
518
|
+
if position != 1:
|
|
519
|
+
raise IndexError(f"Overlay widget focus_position currently must always be set to 1, not {position}")
|
|
520
|
+
|
|
521
|
+
def _get_focus_position(self) -> int | None:
|
|
522
|
+
warnings.warn(
|
|
523
|
+
f"method `{self.__class__.__name__}._get_focus_position` is deprecated, "
|
|
524
|
+
f"please use `{self.__class__.__name__}.focus_position` property",
|
|
525
|
+
DeprecationWarning,
|
|
526
|
+
stacklevel=3,
|
|
527
|
+
)
|
|
528
|
+
return 1
|
|
529
|
+
|
|
530
|
+
def _set_focus_position(self, position: int) -> None:
|
|
531
|
+
"""
|
|
532
|
+
Set the widget in focus.
|
|
533
|
+
|
|
534
|
+
position -- index of child widget to be made focus
|
|
535
|
+
"""
|
|
536
|
+
warnings.warn(
|
|
537
|
+
f"method `{self.__class__.__name__}._set_focus_position` is deprecated, "
|
|
538
|
+
f"please use `{self.__class__.__name__}.focus_position` property",
|
|
539
|
+
DeprecationWarning,
|
|
540
|
+
stacklevel=3,
|
|
541
|
+
)
|
|
542
|
+
if position != 1:
|
|
543
|
+
raise IndexError(f"Overlay widget focus_position currently must always be set to 1, not {position}")
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def contents(self):
|
|
547
|
+
"""
|
|
548
|
+
a list-like object similar to::
|
|
549
|
+
|
|
550
|
+
[(bottom_w, bottom_options)),
|
|
551
|
+
(top_w, top_options)]
|
|
552
|
+
|
|
553
|
+
This object may be used to read or update top and bottom widgets and
|
|
554
|
+
top widgets's options, but no widgets may be added or removed.
|
|
555
|
+
|
|
556
|
+
`top_options` takes the form
|
|
557
|
+
`(align_type, align_amount, width_type, width_amount, min_width, left,
|
|
558
|
+
right, valign_type, valign_amount, height_type, height_amount,
|
|
559
|
+
min_height, top, bottom)`
|
|
560
|
+
|
|
561
|
+
bottom_options is always
|
|
562
|
+
`('left', None, 'relative', 100, None, 0, 0,
|
|
563
|
+
'top', None, 'relative', 100, None, 0, 0)`
|
|
564
|
+
which means that bottom widget always covers the full area of the Overlay.
|
|
565
|
+
writing a different value for `bottom_options` raises an
|
|
566
|
+
:exc:`OverlayError`.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
# noinspection PyMethodParameters
|
|
570
|
+
class OverlayContents(typing.Sequence[typing.Tuple[Widget, OverlayOptions]]):
|
|
571
|
+
# pylint: disable=no-self-argument
|
|
572
|
+
def __len__(inner_self) -> int:
|
|
573
|
+
return 2
|
|
574
|
+
|
|
575
|
+
__getitem__ = self._contents__getitem__
|
|
576
|
+
__setitem__ = self._contents__setitem__
|
|
577
|
+
|
|
578
|
+
def __repr__(inner_self) -> str:
|
|
579
|
+
return repr(f"<{inner_self.__class__.__name__}({[inner_self[0], inner_self[1]]})> for {self}")
|
|
580
|
+
|
|
581
|
+
def __rich_repr__(inner_self) -> Iterator[tuple[str | None, typing.Any] | typing.Any]:
|
|
582
|
+
for val in inner_self:
|
|
583
|
+
yield None, val
|
|
584
|
+
|
|
585
|
+
def __iter__(inner_self) -> Iterator[tuple[Widget, OverlayOptions]]:
|
|
586
|
+
for idx in range(2):
|
|
587
|
+
yield inner_self[idx]
|
|
588
|
+
|
|
589
|
+
return OverlayContents()
|
|
590
|
+
|
|
591
|
+
@contents.setter
|
|
592
|
+
def contents(self, new_contents):
|
|
593
|
+
if len(new_contents) != 2:
|
|
594
|
+
raise ValueError("Contents length for overlay should be only 2")
|
|
595
|
+
self.contents[0] = new_contents[0]
|
|
596
|
+
self.contents[1] = new_contents[1]
|
|
597
|
+
|
|
598
|
+
def _contents__getitem__(self, index: Literal[0, 1]) -> tuple[Widget, OverlayOptions]:
|
|
599
|
+
if index == 0:
|
|
600
|
+
return (self.bottom_w, self._DEFAULT_BOTTOM_OPTIONS)
|
|
601
|
+
|
|
602
|
+
if index == 1:
|
|
603
|
+
return (
|
|
604
|
+
self.top_w,
|
|
605
|
+
OverlayOptions(
|
|
606
|
+
self.align_type,
|
|
607
|
+
self.align_amount,
|
|
608
|
+
self.width_type,
|
|
609
|
+
self.width_amount,
|
|
610
|
+
self.min_width,
|
|
611
|
+
self.left,
|
|
612
|
+
self.right,
|
|
613
|
+
self.valign_type,
|
|
614
|
+
self.valign_amount,
|
|
615
|
+
self.height_type,
|
|
616
|
+
self.height_amount,
|
|
617
|
+
self.min_height,
|
|
618
|
+
self.top,
|
|
619
|
+
self.bottom,
|
|
620
|
+
),
|
|
621
|
+
)
|
|
622
|
+
raise IndexError(f"Overlay.contents has no position {index!r}")
|
|
623
|
+
|
|
624
|
+
def _contents__setitem__(self, index: Literal[0, 1], value: tuple[Widget, OverlayOptions]) -> None:
|
|
625
|
+
try:
|
|
626
|
+
value_w, value_options = value
|
|
627
|
+
except (ValueError, TypeError) as exc:
|
|
628
|
+
raise OverlayError(f"added content invalid: {value!r}").with_traceback(exc.__traceback__) from exc
|
|
629
|
+
if index == 0:
|
|
630
|
+
if value_options != self._DEFAULT_BOTTOM_OPTIONS:
|
|
631
|
+
raise OverlayError(f"bottom_options must be set to {self._DEFAULT_BOTTOM_OPTIONS!r}")
|
|
632
|
+
self.bottom_w = value_w
|
|
633
|
+
elif index == 1:
|
|
634
|
+
try:
|
|
635
|
+
(
|
|
636
|
+
align_type,
|
|
637
|
+
align_amount,
|
|
638
|
+
width_type,
|
|
639
|
+
width_amount,
|
|
640
|
+
min_width,
|
|
641
|
+
left,
|
|
642
|
+
right,
|
|
643
|
+
valign_type,
|
|
644
|
+
valign_amount,
|
|
645
|
+
height_type,
|
|
646
|
+
height_amount,
|
|
647
|
+
min_height,
|
|
648
|
+
top,
|
|
649
|
+
bottom,
|
|
650
|
+
) = value_options
|
|
651
|
+
except (ValueError, TypeError) as exc:
|
|
652
|
+
raise OverlayError(f"top_options is invalid: {value_options!r}").with_traceback(
|
|
653
|
+
exc.__traceback__
|
|
654
|
+
) from exc
|
|
655
|
+
# normalize first, this is where errors are raised
|
|
656
|
+
align_type, align_amount = normalize_align(simplify_align(align_type, align_amount), OverlayError)
|
|
657
|
+
width_type, width_amount = normalize_width(simplify_width(width_type, width_amount), OverlayError)
|
|
658
|
+
valign_type, valign_amount = normalize_valign(simplify_valign(valign_type, valign_amount), OverlayError)
|
|
659
|
+
height_type, height_amount = normalize_height(simplify_height(height_type, height_amount), OverlayError)
|
|
660
|
+
self.align_type = align_type
|
|
661
|
+
self.align_amount = align_amount
|
|
662
|
+
self.width_type = width_type
|
|
663
|
+
self.width_amount = width_amount
|
|
664
|
+
self.valign_type = valign_type
|
|
665
|
+
self.valign_amount = valign_amount
|
|
666
|
+
self.height_type = height_type
|
|
667
|
+
self.height_amount = height_amount
|
|
668
|
+
self.left = left
|
|
669
|
+
self.right = right
|
|
670
|
+
self.top = top
|
|
671
|
+
self.bottom = bottom
|
|
672
|
+
self.min_width = min_width
|
|
673
|
+
self.min_height = min_height
|
|
674
|
+
else:
|
|
675
|
+
raise IndexError(f"Overlay.contents has no position {index!r}")
|
|
676
|
+
self._invalidate()
|
|
677
|
+
|
|
678
|
+
def get_cursor_coords(self, size: tuple[()] | tuple[int] | tuple[int, int]) -> tuple[int, int] | None:
|
|
679
|
+
"""Return cursor coords from top_w, if any."""
|
|
680
|
+
if not hasattr(self.top_w, "get_cursor_coords"):
|
|
681
|
+
return None
|
|
682
|
+
real_size = self.pack(size, True)
|
|
683
|
+
(maxcol, maxrow) = real_size
|
|
684
|
+
left, right, top, bottom = self.calculate_padding_filler(real_size, True)
|
|
685
|
+
x, y = self.top_w.get_cursor_coords((maxcol - left - right, maxrow - top - bottom))
|
|
686
|
+
if y >= maxrow: # required??
|
|
687
|
+
y = maxrow - 1
|
|
688
|
+
return x + left, y + top
|
|
689
|
+
|
|
690
|
+
def calculate_padding_filler(
|
|
691
|
+
self,
|
|
692
|
+
size: tuple[int, int],
|
|
693
|
+
focus: bool,
|
|
694
|
+
) -> tuple[int, int, int, int]:
|
|
695
|
+
"""Return (padding left, right, filler top, bottom)."""
|
|
696
|
+
(maxcol, maxrow) = size
|
|
697
|
+
height = None
|
|
698
|
+
if self.width_type == WHSettings.PACK:
|
|
699
|
+
width, height = self.top_w.pack((), focus=focus)
|
|
700
|
+
if not height:
|
|
701
|
+
raise OverlayError("fixed widget must have a height")
|
|
702
|
+
left, right = calculate_left_right_padding(
|
|
703
|
+
maxcol,
|
|
704
|
+
self.align_type,
|
|
705
|
+
self.align_amount,
|
|
706
|
+
WrapMode.CLIP,
|
|
707
|
+
width,
|
|
708
|
+
None,
|
|
709
|
+
self.left,
|
|
710
|
+
self.right,
|
|
711
|
+
)
|
|
712
|
+
else:
|
|
713
|
+
left, right = calculate_left_right_padding(
|
|
714
|
+
maxcol,
|
|
715
|
+
self.align_type,
|
|
716
|
+
self.align_amount,
|
|
717
|
+
self.width_type,
|
|
718
|
+
self.width_amount,
|
|
719
|
+
self.min_width,
|
|
720
|
+
self.left,
|
|
721
|
+
self.right,
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
if height:
|
|
725
|
+
# top_w is a fixed widget
|
|
726
|
+
top, bottom = calculate_top_bottom_filler(
|
|
727
|
+
maxrow,
|
|
728
|
+
self.valign_type,
|
|
729
|
+
self.valign_amount,
|
|
730
|
+
WHSettings.GIVEN,
|
|
731
|
+
height,
|
|
732
|
+
None,
|
|
733
|
+
self.top,
|
|
734
|
+
self.bottom,
|
|
735
|
+
)
|
|
736
|
+
if maxrow - top - bottom < height:
|
|
737
|
+
bottom = maxrow - top - height
|
|
738
|
+
elif self.height_type == WHSettings.PACK:
|
|
739
|
+
# top_w is a flow widget
|
|
740
|
+
height = self.top_w.rows((maxcol,), focus=focus)
|
|
741
|
+
top, bottom = calculate_top_bottom_filler(
|
|
742
|
+
maxrow,
|
|
743
|
+
self.valign_type,
|
|
744
|
+
self.valign_amount,
|
|
745
|
+
WHSettings.GIVEN,
|
|
746
|
+
height,
|
|
747
|
+
None,
|
|
748
|
+
self.top,
|
|
749
|
+
self.bottom,
|
|
750
|
+
)
|
|
751
|
+
if height > maxrow: # flow widget rendered too large
|
|
752
|
+
bottom = maxrow - height
|
|
753
|
+
else:
|
|
754
|
+
top, bottom = calculate_top_bottom_filler(
|
|
755
|
+
maxrow,
|
|
756
|
+
self.valign_type,
|
|
757
|
+
self.valign_amount,
|
|
758
|
+
self.height_type,
|
|
759
|
+
self.height_amount,
|
|
760
|
+
self.min_height,
|
|
761
|
+
self.top,
|
|
762
|
+
self.bottom,
|
|
763
|
+
)
|
|
764
|
+
return left, right, top, bottom
|
|
765
|
+
|
|
766
|
+
def top_w_size(
|
|
767
|
+
self,
|
|
768
|
+
size: tuple[int, int],
|
|
769
|
+
left: int,
|
|
770
|
+
right: int,
|
|
771
|
+
top: int,
|
|
772
|
+
bottom: int,
|
|
773
|
+
) -> tuple[()] | tuple[int] | tuple[int, int]:
|
|
774
|
+
"""Return the size to pass to top_w."""
|
|
775
|
+
if self.width_type == WHSettings.PACK:
|
|
776
|
+
# top_w is a fixed widget
|
|
777
|
+
return ()
|
|
778
|
+
maxcol, maxrow = size
|
|
779
|
+
if self.width_type != WHSettings.PACK and self.height_type == WHSettings.PACK:
|
|
780
|
+
# top_w is a flow widget
|
|
781
|
+
return (maxcol - left - right,)
|
|
782
|
+
return (maxcol - left - right, maxrow - top - bottom)
|
|
783
|
+
|
|
784
|
+
def render(self, size: tuple[()] | tuple[int] | tuple[int, int], focus: bool = False) -> CompositeCanvas:
|
|
785
|
+
"""Render top_w overlayed on bottom_w."""
|
|
786
|
+
real_size = self.pack(size, focus)
|
|
787
|
+
|
|
788
|
+
left, right, top, bottom = self.calculate_padding_filler(real_size, focus)
|
|
789
|
+
bottom_c = self.bottom_w.render(real_size)
|
|
790
|
+
if not bottom_c.cols() or not bottom_c.rows():
|
|
791
|
+
return CompositeCanvas(bottom_c)
|
|
792
|
+
|
|
793
|
+
top_c = self.top_w.render(self.top_w_size(real_size, left, right, top, bottom), focus)
|
|
794
|
+
top_c = CompositeCanvas(top_c)
|
|
795
|
+
if left < 0 or right < 0:
|
|
796
|
+
top_c.pad_trim_left_right(min(0, left), min(0, right))
|
|
797
|
+
if top < 0 or bottom < 0:
|
|
798
|
+
top_c.pad_trim_top_bottom(min(0, top), min(0, bottom))
|
|
799
|
+
|
|
800
|
+
return CanvasOverlay(top_c, bottom_c, left, top)
|
|
801
|
+
|
|
802
|
+
def mouse_event(
|
|
803
|
+
self,
|
|
804
|
+
size: tuple[()] | tuple[int] | tuple[int, int],
|
|
805
|
+
event: str,
|
|
806
|
+
button: int,
|
|
807
|
+
col: int,
|
|
808
|
+
row: int,
|
|
809
|
+
focus: bool,
|
|
810
|
+
) -> bool | None:
|
|
811
|
+
"""Pass event to top_w, ignore if outside of top_w."""
|
|
812
|
+
if not hasattr(self.top_w, "mouse_event"):
|
|
813
|
+
return False
|
|
814
|
+
|
|
815
|
+
real_size = self.pack(size, focus)
|
|
816
|
+
|
|
817
|
+
left, right, top, bottom = self.calculate_padding_filler(real_size, focus)
|
|
818
|
+
maxcol, maxrow = real_size
|
|
819
|
+
if col < left or col >= maxcol - right or row < top or row >= maxrow - bottom:
|
|
820
|
+
return False
|
|
821
|
+
|
|
822
|
+
return self.top_w.mouse_event(
|
|
823
|
+
self.top_w_size(real_size, left, right, top, bottom),
|
|
824
|
+
event,
|
|
825
|
+
button,
|
|
826
|
+
col - left,
|
|
827
|
+
row - top,
|
|
828
|
+
focus,
|
|
829
|
+
)
|