panelmark 0.1.0__tar.gz

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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Timothy Morris PhD
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,178 @@
1
+ Metadata-Version: 2.2
2
+ Name: panelmark
3
+ Version: 0.1.0
4
+ Summary: A markup language for UI layouts — define panels once, render anywhere.
5
+ Author-email: Timothy Morris PhD <sirrommit@gmail.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Timothy Morris PhD
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/sirrommit/panelmark
29
+ Keywords: ui,layout,dsl,markup,tui,terminal,gui
30
+ Classifier: Development Status :: 3 - Alpha
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: Topic :: Software Development :: User Interfaces
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Requires-Python: >=3.10
39
+ Description-Content-Type: text/markdown
40
+ License-File: LICENSE
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest; extra == "dev"
43
+ Requires-Dist: pytest-cov; extra == "dev"
44
+
45
+ # panelmark
46
+
47
+ **panelmark** is a zero-dependency Python library for defining terminal UI layouts using a readable
48
+ ASCII-art shell language, and for implementing the interaction logic that runs inside them.
49
+
50
+ panelmark is the **core library** in the panelmark ecosystem. It has no runtime dependencies on
51
+ any terminal library. It defines:
52
+
53
+ ---
54
+
55
+ ## What is real today
56
+
57
+ | Feature | Status |
58
+ |---------|--------|
59
+ | Shell definition language (ASCII-art DSL) | ✅ Fully working |
60
+ | `#` line comments and `/* */` block comments | ✅ Fully working |
61
+ | Horizontal splits (`=` / `-` border rows) | ✅ Fully working |
62
+ | Vertical splits — single-line divider (`\|`) | ✅ Fully working |
63
+ | Vertical splits — double-line divider (`\|\|`) | ✅ Fully working |
64
+ | Equal-width fill splits (all columns fill-width) | ✅ Columns share space equally (differ by at most 1 char) |
65
+ | Panel headings (`__text__` syntax) | ⚠️ Parsed and stored; renderers must implement display |
66
+ | `Shell` state machine (focus, dirty tracking, key dispatch, `on_change`, `bind`) | ✅ Fully working |
67
+ | Draw command abstraction (`DrawCommand`, `RenderContext`, `WriteCmd`, `FillCmd`, `CursorCmd`) | ✅ Fully working |
68
+ | `Interaction` base class | ✅ Fully working |
69
+
70
+ See [panelmark-tui limitations](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-tui/limitations.md) for the combined limitations list.
71
+
72
+ ---
73
+
74
+ - The **shell definition language** — an ASCII-art syntax for describing layouts
75
+ - The **layout model** — resolved geometry (row, col, width, height) for each named region
76
+ - The **draw command abstraction** — renderer-agnostic `DrawCommand` types returned by interactions
77
+ - The **`Interaction` base class** — the protocol all interactive widgets implement
78
+ - The **`Shell` state machine** — focus, dirty tracking, key dispatch, and value observation
79
+
80
+ To actually display a shell in a terminal, pair panelmark with
81
+ [**panelmark-tui**](https://github.com/sirrommit/panelmark-tui), which wraps
82
+ [blessed](https://github.com/jquast/blessed) and provides a full event loop, built-in
83
+ interactions, and renderer-specific convenience widgets.
84
+
85
+ ---
86
+
87
+ ## Installation
88
+
89
+ ```
90
+ pip install panelmark
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Quick start
96
+
97
+ ```python
98
+ from panelmark import Shell, Interaction
99
+ from panelmark.draw import DrawCommand, RenderContext, WriteCmd, FillCmd
100
+
101
+ # 1. Define a layout
102
+ LAYOUT = """
103
+ |=== <bold>My App</> ===|
104
+ |{10R $sidebar$ }|{$main$ }|
105
+ |==================|
106
+ |{2R $status$ }|
107
+ |==================|
108
+ """
109
+
110
+ # 2. Implement an interaction
111
+ class Label(Interaction):
112
+ def __init__(self, text: str):
113
+ self._text = text
114
+
115
+ @property
116
+ def is_focusable(self) -> bool:
117
+ return False
118
+
119
+ def render(self, context: RenderContext, focused: bool = False) -> list[DrawCommand]:
120
+ line = self._text[:context.width].ljust(context.width)
121
+ return [WriteCmd(row=0, col=0, text=line)]
122
+
123
+ def handle_key(self, key) -> tuple:
124
+ return False, self.get_value()
125
+
126
+ def get_value(self):
127
+ return self._text
128
+
129
+ def set_value(self, value) -> None:
130
+ self._text = str(value)
131
+
132
+ # 3. Assign interactions to regions
133
+ shell = Shell(LAYOUT)
134
+ shell.assign("sidebar", Label("Navigation"))
135
+ shell.assign("main", Label("Content area"))
136
+ shell.assign("status", Label("Ready"))
137
+
138
+ # 4. Drive with a renderer (e.g. panelmark-tui)
139
+ # result = shell.run() ← panelmark_tui.Shell adds run()
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Documentation
145
+
146
+ | Document | Description |
147
+ |----------|-------------|
148
+ | [Shell Language](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/overview.md) | Shell, region, and panel concepts; state machine methods |
149
+ | [Shell Language Syntax](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/syntax.md) | Full grammar reference for the ASCII-art layout DSL |
150
+ | [Shell Language Examples](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/examples.md) | Annotated examples; custom interactions; portable rendering patterns |
151
+ | [Draw Commands](https://github.com/sirrommit/panelmark-docs/blob/main/docs/renderer-spec/contract.md) | `DrawCommand` types, `RenderContext`, and the style dict |
152
+ | [Custom Interactions](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/examples.md) | Implementing the `Interaction` ABC |
153
+ | [Renderer Spec](https://github.com/sirrommit/panelmark-docs/blob/main/docs/renderer-spec/overview.md) | Renderer compatibility contract, portable library layer, and extension policy |
154
+ | [Ecosystem Overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md) | Layered design; package responsibilities; dependency direction |
155
+ | [Choosing a Renderer](https://github.com/sirrommit/panelmark-docs/blob/main/docs/getting-started.md) | Decision tree for selecting the right package |
156
+
157
+ ---
158
+
159
+ ## Ecosystem
160
+
161
+ panelmark follows a layered design. The core library is renderer-agnostic;
162
+ renderer-specific packages extend it.
163
+
164
+ | Package | Role |
165
+ |---------|------|
166
+ | **panelmark** (this package) | Zero-dependency core: shell language, layout, draw commands, interaction protocol |
167
+ | [**panelmark-tui**](https://github.com/sirrommit/panelmark-tui) | Terminal renderer (blessed); portable-library-compatible |
168
+ | [**panelmark-html**](https://github.com/sirrommit/panelmark-html) | Static HTML/CSS renderer; pre-alpha; foundation for panelmark-web |
169
+ | [**panelmark-web**](https://github.com/sirrommit/panelmark-web) | Live web runtime via WebSockets; portable-library-compatible |
170
+
171
+ See [ecosystem overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md)
172
+ for the full design rationale and dependency diagram.
173
+
174
+ ---
175
+
176
+ ## License
177
+
178
+ MIT
@@ -0,0 +1,134 @@
1
+ # panelmark
2
+
3
+ **panelmark** is a zero-dependency Python library for defining terminal UI layouts using a readable
4
+ ASCII-art shell language, and for implementing the interaction logic that runs inside them.
5
+
6
+ panelmark is the **core library** in the panelmark ecosystem. It has no runtime dependencies on
7
+ any terminal library. It defines:
8
+
9
+ ---
10
+
11
+ ## What is real today
12
+
13
+ | Feature | Status |
14
+ |---------|--------|
15
+ | Shell definition language (ASCII-art DSL) | ✅ Fully working |
16
+ | `#` line comments and `/* */` block comments | ✅ Fully working |
17
+ | Horizontal splits (`=` / `-` border rows) | ✅ Fully working |
18
+ | Vertical splits — single-line divider (`\|`) | ✅ Fully working |
19
+ | Vertical splits — double-line divider (`\|\|`) | ✅ Fully working |
20
+ | Equal-width fill splits (all columns fill-width) | ✅ Columns share space equally (differ by at most 1 char) |
21
+ | Panel headings (`__text__` syntax) | ⚠️ Parsed and stored; renderers must implement display |
22
+ | `Shell` state machine (focus, dirty tracking, key dispatch, `on_change`, `bind`) | ✅ Fully working |
23
+ | Draw command abstraction (`DrawCommand`, `RenderContext`, `WriteCmd`, `FillCmd`, `CursorCmd`) | ✅ Fully working |
24
+ | `Interaction` base class | ✅ Fully working |
25
+
26
+ See [panelmark-tui limitations](https://github.com/sirrommit/panelmark-docs/blob/main/docs/panelmark-tui/limitations.md) for the combined limitations list.
27
+
28
+ ---
29
+
30
+ - The **shell definition language** — an ASCII-art syntax for describing layouts
31
+ - The **layout model** — resolved geometry (row, col, width, height) for each named region
32
+ - The **draw command abstraction** — renderer-agnostic `DrawCommand` types returned by interactions
33
+ - The **`Interaction` base class** — the protocol all interactive widgets implement
34
+ - The **`Shell` state machine** — focus, dirty tracking, key dispatch, and value observation
35
+
36
+ To actually display a shell in a terminal, pair panelmark with
37
+ [**panelmark-tui**](https://github.com/sirrommit/panelmark-tui), which wraps
38
+ [blessed](https://github.com/jquast/blessed) and provides a full event loop, built-in
39
+ interactions, and renderer-specific convenience widgets.
40
+
41
+ ---
42
+
43
+ ## Installation
44
+
45
+ ```
46
+ pip install panelmark
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Quick start
52
+
53
+ ```python
54
+ from panelmark import Shell, Interaction
55
+ from panelmark.draw import DrawCommand, RenderContext, WriteCmd, FillCmd
56
+
57
+ # 1. Define a layout
58
+ LAYOUT = """
59
+ |=== <bold>My App</> ===|
60
+ |{10R $sidebar$ }|{$main$ }|
61
+ |==================|
62
+ |{2R $status$ }|
63
+ |==================|
64
+ """
65
+
66
+ # 2. Implement an interaction
67
+ class Label(Interaction):
68
+ def __init__(self, text: str):
69
+ self._text = text
70
+
71
+ @property
72
+ def is_focusable(self) -> bool:
73
+ return False
74
+
75
+ def render(self, context: RenderContext, focused: bool = False) -> list[DrawCommand]:
76
+ line = self._text[:context.width].ljust(context.width)
77
+ return [WriteCmd(row=0, col=0, text=line)]
78
+
79
+ def handle_key(self, key) -> tuple:
80
+ return False, self.get_value()
81
+
82
+ def get_value(self):
83
+ return self._text
84
+
85
+ def set_value(self, value) -> None:
86
+ self._text = str(value)
87
+
88
+ # 3. Assign interactions to regions
89
+ shell = Shell(LAYOUT)
90
+ shell.assign("sidebar", Label("Navigation"))
91
+ shell.assign("main", Label("Content area"))
92
+ shell.assign("status", Label("Ready"))
93
+
94
+ # 4. Drive with a renderer (e.g. panelmark-tui)
95
+ # result = shell.run() ← panelmark_tui.Shell adds run()
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Documentation
101
+
102
+ | Document | Description |
103
+ |----------|-------------|
104
+ | [Shell Language](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/overview.md) | Shell, region, and panel concepts; state machine methods |
105
+ | [Shell Language Syntax](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/syntax.md) | Full grammar reference for the ASCII-art layout DSL |
106
+ | [Shell Language Examples](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/examples.md) | Annotated examples; custom interactions; portable rendering patterns |
107
+ | [Draw Commands](https://github.com/sirrommit/panelmark-docs/blob/main/docs/renderer-spec/contract.md) | `DrawCommand` types, `RenderContext`, and the style dict |
108
+ | [Custom Interactions](https://github.com/sirrommit/panelmark-docs/blob/main/docs/shell-language/examples.md) | Implementing the `Interaction` ABC |
109
+ | [Renderer Spec](https://github.com/sirrommit/panelmark-docs/blob/main/docs/renderer-spec/overview.md) | Renderer compatibility contract, portable library layer, and extension policy |
110
+ | [Ecosystem Overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md) | Layered design; package responsibilities; dependency direction |
111
+ | [Choosing a Renderer](https://github.com/sirrommit/panelmark-docs/blob/main/docs/getting-started.md) | Decision tree for selecting the right package |
112
+
113
+ ---
114
+
115
+ ## Ecosystem
116
+
117
+ panelmark follows a layered design. The core library is renderer-agnostic;
118
+ renderer-specific packages extend it.
119
+
120
+ | Package | Role |
121
+ |---------|------|
122
+ | **panelmark** (this package) | Zero-dependency core: shell language, layout, draw commands, interaction protocol |
123
+ | [**panelmark-tui**](https://github.com/sirrommit/panelmark-tui) | Terminal renderer (blessed); portable-library-compatible |
124
+ | [**panelmark-html**](https://github.com/sirrommit/panelmark-html) | Static HTML/CSS renderer; pre-alpha; foundation for panelmark-web |
125
+ | [**panelmark-web**](https://github.com/sirrommit/panelmark-web) | Live web runtime via WebSockets; portable-library-compatible |
126
+
127
+ See [ecosystem overview](https://github.com/sirrommit/panelmark-docs/blob/main/docs/ecosystem.md)
128
+ for the full design rationale and dependency diagram.
129
+
130
+ ---
131
+
132
+ ## License
133
+
134
+ MIT
@@ -0,0 +1,4 @@
1
+ from .shell import Shell
2
+ from .interactions import Interaction
3
+
4
+ __all__ = ["Shell", "Interaction"]
@@ -0,0 +1,182 @@
1
+ """Draw commands and render context for the panelmark renderer abstraction.
2
+
3
+ Interactions return a ``list[DrawCommand]`` from their ``render()`` method
4
+ rather than performing side effects directly. Each renderer provides an
5
+ executor that translates the command list to its output surface (terminal,
6
+ HTML, etc.).
7
+
8
+ Coordinate system
9
+ -----------------
10
+ All row and column values in draw commands are **region-relative**:
11
+ ``(0, 0)`` is the top-left cell of the interaction's assigned region.
12
+ The executor maps these to screen-absolute or document-absolute coordinates
13
+ internally. Interactions never need to know their absolute position.
14
+
15
+ Style dict
16
+ ----------
17
+ The optional ``style`` argument on ``WriteCmd`` and ``FillCmd`` is a plain
18
+ dict. All keys are optional. Renderers apply the keys they support and ignore
19
+ the rest — unknown keys are not an error.
20
+
21
+ Valid keys and values::
22
+
23
+ bold bool — bold / heavy weight
24
+ italic bool — italic (renderers that lack italic use normal)
25
+ underline bool — underline
26
+ reverse bool — swap foreground and background colours
27
+ color str — foreground colour name: 'red', 'green', 'yellow',
28
+ 'blue', 'magenta', 'cyan', 'white', 'black'
29
+ bg str — background colour name (same values as color)
30
+
31
+ Example — a minimal custom render() implementation::
32
+
33
+ from panelmark.draw import WriteCmd, FillCmd, RenderContext, DrawCommand
34
+
35
+ def render(self, context: RenderContext, focused: bool = False) -> list[DrawCommand]:
36
+ text = self._value[:context.width].ljust(context.width)
37
+ style = {'reverse': True} if focused else None
38
+ return [
39
+ FillCmd(row=0, col=0, width=context.width, height=context.height),
40
+ WriteCmd(row=0, col=0, text=text, style=style),
41
+ ]
42
+ """
43
+
44
+ from __future__ import annotations
45
+
46
+ from dataclasses import dataclass, field
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class RenderContext:
51
+ """Read-only rendering context passed to ``Interaction.render()``.
52
+
53
+ Carries the dimensions of the interaction's assigned region and a set
54
+ of capability flags describing what the current renderer supports.
55
+ Interactions use ``supports()`` to degrade gracefully on renderers that
56
+ lack a capability rather than failing or producing garbled output.
57
+
58
+ Attributes
59
+ ----------
60
+ width:
61
+ Width of the region in character columns.
62
+ height:
63
+ Height of the region in character rows.
64
+ capabilities:
65
+ Frozenset of feature strings supported by the renderer. Do not
66
+ inspect this directly — use ``supports(feature)`` instead.
67
+ """
68
+
69
+ width: int
70
+ height: int
71
+ capabilities: frozenset[str] = field(default_factory=frozenset)
72
+
73
+ def supports(self, feature: str) -> bool:
74
+ """Return True if the renderer supports *feature*.
75
+
76
+ Known feature strings (renderers may support any subset):
77
+
78
+ ``'color'``
79
+ At least 8 foreground/background colours are available.
80
+ ``'256color'``
81
+ 256-colour palette is available.
82
+ ``'truecolor'``
83
+ 24-bit (16 million colour) palette is available.
84
+ ``'unicode'``
85
+ Unicode characters (box-drawing, block elements, etc.) render
86
+ correctly. ASCII-only renderers do not set this.
87
+ ``'cursor'``
88
+ A text cursor can be positioned within the region. Set by TUI
89
+ renderers and interactive web renderers; not set by static HTML.
90
+ ``'italic'``
91
+ Italic text is visually distinct from normal weight text.
92
+
93
+ Returns False for unknown or unsupported feature strings.
94
+ """
95
+ return feature in self.capabilities
96
+
97
+
98
+ @dataclass
99
+ class WriteCmd:
100
+ """Write styled text at region-relative (row, col).
101
+
102
+ The text is written left-to-right starting at the given position.
103
+ No automatic clipping is performed — the executor clips to the region
104
+ boundary. Callers should ensure text does not exceed ``context.width``
105
+ columns from ``col``.
106
+
107
+ Parameters
108
+ ----------
109
+ row:
110
+ Zero-based row offset from the top of the region.
111
+ col:
112
+ Zero-based column offset from the left of the region.
113
+ text:
114
+ The string to write. Should not contain newlines.
115
+ style:
116
+ Optional style dict. See module docstring for valid keys.
117
+ """
118
+
119
+ row: int
120
+ col: int
121
+ text: str
122
+ style: dict | None = None
123
+
124
+
125
+ @dataclass
126
+ class FillCmd:
127
+ """Fill a rectangle with a repeated character, optionally styled.
128
+
129
+ Useful for clearing a region before writing content, or for drawing
130
+ a styled background block.
131
+
132
+ Parameters
133
+ ----------
134
+ row:
135
+ Zero-based row offset of the top-left corner.
136
+ col:
137
+ Zero-based column offset of the top-left corner.
138
+ width:
139
+ Number of columns to fill.
140
+ height:
141
+ Number of rows to fill.
142
+ char:
143
+ The character to fill with. Defaults to a space (blank/clear).
144
+ style:
145
+ Optional style dict. See module docstring for valid keys.
146
+ """
147
+
148
+ row: int
149
+ col: int
150
+ width: int
151
+ height: int
152
+ char: str = ' '
153
+ style: dict | None = None
154
+
155
+
156
+ @dataclass
157
+ class CursorCmd:
158
+ """Hint: place the text cursor at region-relative (row, col).
159
+
160
+ This is a positioning hint, not a draw operation. Renderers that
161
+ support a visible text cursor (``context.supports('cursor')``) move
162
+ the cursor here after executing all other commands in the list.
163
+ Renderers that do not support a cursor ignore this command entirely.
164
+
165
+ There should be at most one ``CursorCmd`` per command list. If multiple
166
+ are present, the executor uses the last one.
167
+
168
+ Parameters
169
+ ----------
170
+ row:
171
+ Zero-based row offset from the top of the region.
172
+ col:
173
+ Zero-based column offset from the left of the region.
174
+ """
175
+
176
+ row: int
177
+ col: int
178
+
179
+
180
+ #: Union type alias for the three command types.
181
+ #: A ``render()`` method returns ``list[DrawCommand]``.
182
+ DrawCommand = WriteCmd | FillCmd | CursorCmd
@@ -0,0 +1,13 @@
1
+ class ShellSyntaxError(Exception):
2
+ def __init__(self, message, line=None):
3
+ self.message = message
4
+ self.line = line # 1-based line number or None
5
+ super().__init__(f"line {line}: {message}" if line else message)
6
+
7
+
8
+ class RegionNotFoundError(Exception):
9
+ pass
10
+
11
+
12
+ class CircularUpdateError(Exception):
13
+ pass
@@ -0,0 +1,4 @@
1
+ from .base import Interaction
2
+ from panelmark.draw import DrawCommand, RenderContext, WriteCmd, FillCmd, CursorCmd
3
+
4
+ __all__ = ["Interaction", "DrawCommand", "RenderContext", "WriteCmd", "FillCmd", "CursorCmd"]
@@ -0,0 +1,63 @@
1
+ from abc import ABC, abstractmethod
2
+ from panelmark.draw import DrawCommand, RenderContext
3
+
4
+
5
+ class Interaction(ABC):
6
+ _shell = None # Set by Shell.assign()
7
+
8
+ @property
9
+ def is_focusable(self) -> bool:
10
+ """Return True if this interaction can meaningfully receive keyboard focus.
11
+ Display-only interactions override this to return False."""
12
+ return True
13
+
14
+ @abstractmethod
15
+ def render(self, context: RenderContext, focused: bool = False) -> list[DrawCommand]:
16
+ """Return draw commands describing the current visual state of this interaction.
17
+
18
+ Commands use region-relative coordinates: ``(0, 0)`` is the top-left
19
+ cell of this interaction's assigned region. The renderer maps them to
20
+ screen-absolute positions when executing via its command executor.
21
+
22
+ The returned list should be a complete description of the interaction's
23
+ visual state for the given context dimensions. Partial updates are not
24
+ supported — callers may skip calling ``render()`` for regions they
25
+ determine are unchanged, so the list must always be fully self-contained.
26
+
27
+ Parameters
28
+ ----------
29
+ context:
30
+ Rendering context carrying region dimensions (``context.width``,
31
+ ``context.height``) and renderer capability flags. Use
32
+ ``context.supports(feature)`` to degrade gracefully on renderers
33
+ that lack a capability.
34
+ focused:
35
+ True if this interaction currently has keyboard focus.
36
+ """
37
+ ...
38
+
39
+ @abstractmethod
40
+ def handle_key(self, key) -> tuple:
41
+ """
42
+ Handle a keypress.
43
+ Returns (value_changed, new_value).
44
+ new_value is whatever Shell.get returns.
45
+ """
46
+ ...
47
+
48
+ @abstractmethod
49
+ def get_value(self):
50
+ """Return the current value of this interaction."""
51
+ ...
52
+
53
+ @abstractmethod
54
+ def set_value(self, value) -> None:
55
+ """Set the current value of this interaction."""
56
+ ...
57
+
58
+ def signal_return(self) -> tuple:
59
+ """
60
+ Called after handle_key to check if this interaction wants Shell.run() to return.
61
+ Returns (should_exit, return_value).
62
+ """
63
+ return False, None