zettcode-tui 0.4.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.
Files changed (83) hide show
  1. zettcode/tui/__init__.py +422 -0
  2. zettcode/tui/_compat.py +32 -0
  3. zettcode/tui/capabilities.py +107 -0
  4. zettcode/tui/clipboard.py +91 -0
  5. zettcode/tui/console.py +229 -0
  6. zettcode/tui/core/__init__.py +63 -0
  7. zettcode/tui/core/app.py +658 -0
  8. zettcode/tui/core/events.py +134 -0
  9. zettcode/tui/core/focus.py +85 -0
  10. zettcode/tui/core/geometry.py +212 -0
  11. zettcode/tui/core/host.py +66 -0
  12. zettcode/tui/core/keymap.py +218 -0
  13. zettcode/tui/core/scheduler.py +105 -0
  14. zettcode/tui/core/screen.py +80 -0
  15. zettcode/tui/core/theme.py +248 -0
  16. zettcode/tui/core/widget.py +204 -0
  17. zettcode/tui/gallery.py +588 -0
  18. zettcode/tui/glyphs.py +73 -0
  19. zettcode/tui/input/__init__.py +18 -0
  20. zettcode/tui/input/bridge.py +46 -0
  21. zettcode/tui/input/decoder.py +234 -0
  22. zettcode/tui/input/events.py +64 -0
  23. zettcode/tui/input/keys.py +289 -0
  24. zettcode/tui/input/reader.py +147 -0
  25. zettcode/tui/layout/__init__.py +24 -0
  26. zettcode/tui/layout/box.py +153 -0
  27. zettcode/tui/layout/overlay.py +164 -0
  28. zettcode/tui/layout/scroll.py +477 -0
  29. zettcode/tui/layout/solver.py +95 -0
  30. zettcode/tui/layout/spacing.py +137 -0
  31. zettcode/tui/py.typed +0 -0
  32. zettcode/tui/render/__init__.py +71 -0
  33. zettcode/tui/render/ansi.py +371 -0
  34. zettcode/tui/render/canvas.py +153 -0
  35. zettcode/tui/render/code.py +456 -0
  36. zettcode/tui/render/color.py +177 -0
  37. zettcode/tui/render/renderer.py +151 -0
  38. zettcode/tui/render/rich_text.py +80 -0
  39. zettcode/tui/render/style.py +121 -0
  40. zettcode/tui/render/sweep.py +87 -0
  41. zettcode/tui/render/text.py +319 -0
  42. zettcode/tui/runner.py +316 -0
  43. zettcode/tui/terminal.py +222 -0
  44. zettcode/tui/testing/__init__.py +6 -0
  45. zettcode/tui/testing/harness.py +220 -0
  46. zettcode/tui/testing/snapshot.py +64 -0
  47. zettcode/tui/theme_file.py +300 -0
  48. zettcode/tui/themes/ayu-mirage.toml +44 -0
  49. zettcode/tui/themes/catppuccin-latte.toml +44 -0
  50. zettcode/tui/themes/catppuccin-mocha.toml +44 -0
  51. zettcode/tui/themes/dracula.toml +44 -0
  52. zettcode/tui/themes/github-dimmed.toml +44 -0
  53. zettcode/tui/themes/github-light.toml +44 -0
  54. zettcode/tui/themes/gruvbox-dark.toml +44 -0
  55. zettcode/tui/themes/gruvbox-light.toml +44 -0
  56. zettcode/tui/themes/kanagawa.toml +44 -0
  57. zettcode/tui/themes/nord.toml +44 -0
  58. zettcode/tui/themes/one-dark.toml +44 -0
  59. zettcode/tui/themes/rose-pine-dawn.toml +44 -0
  60. zettcode/tui/themes/rose-pine.toml +44 -0
  61. zettcode/tui/themes/solarized-dark.toml +44 -0
  62. zettcode/tui/themes/solarized-light.toml +44 -0
  63. zettcode/tui/themes/tokyo-night.toml +44 -0
  64. zettcode/tui/widgets/__init__.py +121 -0
  65. zettcode/tui/widgets/collapsible.py +116 -0
  66. zettcode/tui/widgets/completion.py +211 -0
  67. zettcode/tui/widgets/console.py +442 -0
  68. zettcode/tui/widgets/dialog.py +165 -0
  69. zettcode/tui/widgets/diff.py +236 -0
  70. zettcode/tui/widgets/list.py +293 -0
  71. zettcode/tui/widgets/list_page.py +121 -0
  72. zettcode/tui/widgets/markdown.py +657 -0
  73. zettcode/tui/widgets/progress.py +166 -0
  74. zettcode/tui/widgets/rich_text.py +84 -0
  75. zettcode/tui/widgets/status.py +106 -0
  76. zettcode/tui/widgets/table.py +101 -0
  77. zettcode/tui/widgets/tasks.py +130 -0
  78. zettcode/tui/widgets/text.py +111 -0
  79. zettcode/tui/widgets/textarea.py +671 -0
  80. zettcode/tui/widgets/toast.py +137 -0
  81. zettcode_tui-0.4.0.dist-info/METADATA +20 -0
  82. zettcode_tui-0.4.0.dist-info/RECORD +83 -0
  83. zettcode_tui-0.4.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,111 @@
1
+ """Static display primitives: a label and a horizontal rule."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import TypeAlias
7
+
8
+ from zettcode.tui import RULE
9
+
10
+ from ..core.geometry import Constraints, Size
11
+ from ..core.widget import Widget
12
+ from ..render import Alignment, Canvas, Span, Style, TextLine
13
+ from .rich_text import RichText
14
+
15
+ Value: TypeAlias = str | Callable[[], str]
16
+
17
+
18
+ class Text(RichText):
19
+ """A label that may compute its value at paint time and wrap to its width.
20
+
21
+ Shape::
22
+
23
+ bold heading <- bold=True
24
+ muted caption that wraps across <- muted=True, wrapped at rect.width
25
+ rows
26
+ right aligned <- align="right"
27
+
28
+ It uses the same styled-line layout as :class:`RichText`; the label never
29
+ grows past the rectangle it was given, it just writes fewer rows.
30
+ """
31
+
32
+ #: The label resolves to text only, never to styled fragments.
33
+ value: Value
34
+
35
+ def __init__(
36
+ self,
37
+ value: Value = "",
38
+ *,
39
+ align: Alignment = "left",
40
+ bold: bool = False,
41
+ muted: bool = False,
42
+ ) -> None:
43
+ """Configure the label's content and its paint-time styling.
44
+
45
+ Args:
46
+ value: Static text, or a callback re-evaluated on every paint so the
47
+ label can mirror live state without the owner calling invalidate.
48
+ align: ``"left"``, ``"center"``, or ``"right"`` within the widget
49
+ rectangle, as :func:`layout_rich_lines` implements them; a value
50
+ the layout does not know raises rather than falling back, so a
51
+ caller that misspells one hears about it. Wrapping is unaffected.
52
+ bold: Draw every span bold.
53
+ muted: Use the theme's muted colour instead of the body text colour.
54
+ """
55
+ super().__init__(value, align=align)
56
+ self.value = value
57
+ self.bold = bold
58
+ self.muted = muted
59
+
60
+ @property
61
+ def content(self) -> str:
62
+ """Resolve the current text, calling the value when it is a provider."""
63
+ return self.value() if callable(self.value) else self.value
64
+
65
+ def style_for(self) -> Style:
66
+ """Return the style this label paints with."""
67
+ theme = self.theme
68
+ return Style(foreground=theme.muted if self.muted else theme.text, bold=self.bold)
69
+
70
+ def _lines(self) -> tuple[TextLine, ...]:
71
+ """Return the label as styled lines for the shared layout."""
72
+ style = self.style_for()
73
+ return tuple(TextLine((Span(source, style),)) for source in self.content.split("\n"))
74
+
75
+
76
+ class Rule(Widget):
77
+ """A one-row horizontal separator.
78
+
79
+ Shape::
80
+
81
+ -------------------------------- <- character repeated rect.width times
82
+
83
+ Drawn with U+2500 by default. It asks for a single cell and takes whatever
84
+ width its slot gives it, so it is normally used as ``Slot(Rule(), size=1)``
85
+ between two regions.
86
+ """
87
+
88
+ def __init__(self, character: str = RULE) -> None:
89
+ """Set the glyph the rule repeats across its width.
90
+
91
+ Args:
92
+ character: Repeated once per column; the default is a box-drawing dash.
93
+ """
94
+ super().__init__()
95
+ self.character = character
96
+
97
+ def measure(self, constraints: Constraints) -> Size:
98
+ """Claim a single cell; the parent box supplies the real width."""
99
+ return constraints.constrain(Size(1, 1))
100
+
101
+ def render(self, canvas: Canvas) -> None:
102
+ """Fill the assigned row with the rule character."""
103
+ if self.rect.empty:
104
+ return
105
+ canvas.draw_text(
106
+ self.rect.x,
107
+ self.rect.y,
108
+ self.character * self.rect.width,
109
+ Style(foreground=self.theme.border),
110
+ max_width=self.rect.width,
111
+ )