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.
- panelmark-0.1.0/LICENSE +21 -0
- panelmark-0.1.0/PKG-INFO +178 -0
- panelmark-0.1.0/README.md +134 -0
- panelmark-0.1.0/panelmark/__init__.py +4 -0
- panelmark-0.1.0/panelmark/draw.py +182 -0
- panelmark-0.1.0/panelmark/exceptions.py +13 -0
- panelmark-0.1.0/panelmark/interactions/__init__.py +4 -0
- panelmark-0.1.0/panelmark/interactions/base.py +63 -0
- panelmark-0.1.0/panelmark/layout.py +362 -0
- panelmark-0.1.0/panelmark/observer.py +49 -0
- panelmark-0.1.0/panelmark/parser.py +279 -0
- panelmark-0.1.0/panelmark/shell.py +239 -0
- panelmark-0.1.0/panelmark/style.py +118 -0
- panelmark-0.1.0/panelmark.egg-info/PKG-INFO +178 -0
- panelmark-0.1.0/panelmark.egg-info/SOURCES.txt +23 -0
- panelmark-0.1.0/panelmark.egg-info/dependency_links.txt +1 -0
- panelmark-0.1.0/panelmark.egg-info/requires.txt +4 -0
- panelmark-0.1.0/panelmark.egg-info/top_level.txt +1 -0
- panelmark-0.1.0/pyproject.toml +37 -0
- panelmark-0.1.0/setup.cfg +4 -0
- panelmark-0.1.0/tests/test_layout.py +329 -0
- panelmark-0.1.0/tests/test_observer.py +115 -0
- panelmark-0.1.0/tests/test_parser.py +277 -0
- panelmark-0.1.0/tests/test_shell.py +302 -0
- panelmark-0.1.0/tests/test_style.py +148 -0
panelmark-0.1.0/LICENSE
ADDED
|
@@ -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.
|
panelmark-0.1.0/PKG-INFO
ADDED
|
@@ -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,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,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
|