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.

Files changed (75) hide show
  1. urwid/__init__.py +333 -0
  2. urwid/canvas.py +1413 -0
  3. urwid/command_map.py +137 -0
  4. urwid/container.py +59 -0
  5. urwid/decoration.py +65 -0
  6. urwid/display/__init__.py +97 -0
  7. urwid/display/_posix_raw_display.py +413 -0
  8. urwid/display/_raw_display_base.py +914 -0
  9. urwid/display/_web.css +12 -0
  10. urwid/display/_web.js +462 -0
  11. urwid/display/_win32.py +171 -0
  12. urwid/display/_win32_raw_display.py +269 -0
  13. urwid/display/common.py +1219 -0
  14. urwid/display/curses.py +690 -0
  15. urwid/display/escape.py +624 -0
  16. urwid/display/html_fragment.py +251 -0
  17. urwid/display/lcd.py +518 -0
  18. urwid/display/raw.py +37 -0
  19. urwid/display/web.py +636 -0
  20. urwid/event_loop/__init__.py +55 -0
  21. urwid/event_loop/abstract_loop.py +175 -0
  22. urwid/event_loop/asyncio_loop.py +231 -0
  23. urwid/event_loop/glib_loop.py +294 -0
  24. urwid/event_loop/main_loop.py +721 -0
  25. urwid/event_loop/select_loop.py +230 -0
  26. urwid/event_loop/tornado_loop.py +206 -0
  27. urwid/event_loop/trio_loop.py +302 -0
  28. urwid/event_loop/twisted_loop.py +269 -0
  29. urwid/event_loop/zmq_loop.py +275 -0
  30. urwid/font.py +695 -0
  31. urwid/graphics.py +96 -0
  32. urwid/highlight.css +19 -0
  33. urwid/listbox.py +1899 -0
  34. urwid/monitored_list.py +522 -0
  35. urwid/numedit.py +376 -0
  36. urwid/signals.py +330 -0
  37. urwid/split_repr.py +130 -0
  38. urwid/str_util.py +358 -0
  39. urwid/text_layout.py +632 -0
  40. urwid/treetools.py +515 -0
  41. urwid/util.py +557 -0
  42. urwid/version.py +16 -0
  43. urwid/vterm.py +1806 -0
  44. urwid/widget/__init__.py +181 -0
  45. urwid/widget/attr_map.py +161 -0
  46. urwid/widget/attr_wrap.py +140 -0
  47. urwid/widget/bar_graph.py +649 -0
  48. urwid/widget/big_text.py +77 -0
  49. urwid/widget/box_adapter.py +126 -0
  50. urwid/widget/columns.py +1145 -0
  51. urwid/widget/constants.py +574 -0
  52. urwid/widget/container.py +227 -0
  53. urwid/widget/divider.py +110 -0
  54. urwid/widget/edit.py +718 -0
  55. urwid/widget/filler.py +403 -0
  56. urwid/widget/frame.py +539 -0
  57. urwid/widget/grid_flow.py +539 -0
  58. urwid/widget/line_box.py +194 -0
  59. urwid/widget/overlay.py +829 -0
  60. urwid/widget/padding.py +597 -0
  61. urwid/widget/pile.py +971 -0
  62. urwid/widget/popup.py +170 -0
  63. urwid/widget/progress_bar.py +141 -0
  64. urwid/widget/scrollable.py +597 -0
  65. urwid/widget/solid_fill.py +44 -0
  66. urwid/widget/text.py +354 -0
  67. urwid/widget/widget.py +852 -0
  68. urwid/widget/widget_decoration.py +166 -0
  69. urwid/widget/wimp.py +792 -0
  70. urwid/wimp.py +23 -0
  71. urwid-2.6.0.post0.dist-info/COPYING +504 -0
  72. urwid-2.6.0.post0.dist-info/METADATA +332 -0
  73. urwid-2.6.0.post0.dist-info/RECORD +75 -0
  74. urwid-2.6.0.post0.dist-info/WHEEL +5 -0
  75. urwid-2.6.0.post0.dist-info/top_level.txt +1 -0
@@ -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
+ )