nextpytk 0.3.0__tar.gz → 0.4.1__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.
- nextpytk-0.4.1/PKG-INFO +412 -0
- nextpytk-0.3.0/PKG-INFO → nextpytk-0.4.1/README.md +138 -51
- nextpytk-0.4.1/ROADMAP.md +147 -0
- nextpytk-0.4.1/pyproject.toml +49 -0
- nextpytk-0.4.1/src/nextpytk/__init__.py +50 -0
- {nextpytk-0.3.0 → nextpytk-0.4.1}/src/nextpytk/app.py +1724 -269
- {nextpytk-0.3.0 → nextpytk-0.4.1}/src/nextpytk/layout.py +262 -58
- nextpytk-0.4.1/src/nextpytk/theme.py +617 -0
- nextpytk-0.4.1/src/nextpytk/tokens.py +140 -0
- nextpytk-0.4.1/src/nextpytk/types.py +436 -0
- {nextpytk-0.3.0 → nextpytk-0.4.1}/src/nextpytk/widgets.py +2 -2
- nextpytk-0.3.0/README.md +0 -290
- nextpytk-0.3.0/ROADMAP.md +0 -87
- nextpytk-0.3.0/pyproject.toml +0 -34
- nextpytk-0.3.0/src/nextpytk/__init__.py +0 -8
- nextpytk-0.3.0/src/nextpytk/types.py +0 -176
- {nextpytk-0.3.0 → nextpytk-0.4.1}/.gitignore +0 -0
- {nextpytk-0.3.0 → nextpytk-0.4.1}/LICENSE +0 -0
nextpytk-0.4.1/PKG-INFO
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nextpytk
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Accessible, declarative Tkinter applications from ordinary Python functions.
|
|
5
|
+
Project-URL: Homepage, https://github.com/nishimotz/nextpytk
|
|
6
|
+
Project-URL: Repository, https://github.com/nishimotz/nextpytk
|
|
7
|
+
Project-URL: Issues, https://github.com/nishimotz/nextpytk/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/nishimotz/nextpytk/blob/main/ROADMAP.md
|
|
9
|
+
Author: Takuya Nishimoto
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: accessibility,declarative-ui,gui,tkinter,ttk
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
20
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# nextpytk
|
|
26
|
+
|
|
27
|
+
Accessible, declarative Tkinter applications from ordinary Python functions.
|
|
28
|
+
|
|
29
|
+
Register widgets as plain Python functions, declare layout separately, and
|
|
30
|
+
keep roles/descriptions in one place. The decorator style is Flask-inspired;
|
|
31
|
+
`schema()` exports the same structure for agents and tools.
|
|
32
|
+
Uses ttk widgets where available.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from nextpytk import TkApp, Layout
|
|
40
|
+
|
|
41
|
+
app = TkApp(title="Hello")
|
|
42
|
+
|
|
43
|
+
@app.status("msg")
|
|
44
|
+
def msg():
|
|
45
|
+
return "Hello, world!"
|
|
46
|
+
|
|
47
|
+
@app.button("greet", label="Greet")
|
|
48
|
+
def on_greet(values):
|
|
49
|
+
return {"msg": "Button clicked!"}
|
|
50
|
+
|
|
51
|
+
app.run(layout=Layout().section("msg").section("greet"))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Three layout styles — pick the one that fits:**
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# 1) Simple list (easiest)
|
|
58
|
+
app.run(layout=["msg", "greet"])
|
|
59
|
+
|
|
60
|
+
# 2) Fluent DSL
|
|
61
|
+
app.run(layout=Layout().section("msg").section("greet"))
|
|
62
|
+
|
|
63
|
+
# 3) with-block (context manager)
|
|
64
|
+
with app.layout() as b:
|
|
65
|
+
b.section("msg")
|
|
66
|
+
b.section("greet")
|
|
67
|
+
app.run(layout=b.build())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Design System
|
|
73
|
+
|
|
74
|
+
nextpytk ships with typed constants and design tokens. Prefer them over raw tkinter strings and magic numbers so every app stays consistent and IDE completion works out of the box.
|
|
75
|
+
|
|
76
|
+
### Typed Constants
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from nextpytk.types import Side, Fill, Sticky, State, Orient
|
|
80
|
+
|
|
81
|
+
Layout().section("msg", side=Side.LEFT, fill=Fill.X)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Type | Namespace | Example |
|
|
85
|
+
|------|-----------|---------|
|
|
86
|
+
| `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
|
|
87
|
+
| `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
|
|
88
|
+
| `Sticky` | `Sticky.NSEW/EW/NS/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
|
|
89
|
+
| `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
|
|
90
|
+
| `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale / paned orientation |
|
|
91
|
+
| `Relief` | `Relief.FLAT/RAISED/SUNKEN/...` | border style |
|
|
92
|
+
| `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
|
|
93
|
+
| `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
|
|
94
|
+
|
|
95
|
+
Each type has a matching `*Like` literal alias (e.g. `FillLike`), so raw strings still work when needed.
|
|
96
|
+
|
|
97
|
+
### Spacing tokens
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from nextpytk.tokens import SPACE
|
|
101
|
+
|
|
102
|
+
SPACE[1] # 4px — small padding inside a widget
|
|
103
|
+
SPACE[2] # 8px — adjacent widgets
|
|
104
|
+
SPACE[3] # 12px — section inner gaps
|
|
105
|
+
SPACE[4] # 16px — section margins
|
|
106
|
+
SPACE[6] # 24px — large blocks
|
|
107
|
+
SPACE[8] # 32px — page-level gaps
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The scale is based on a 4px unit and only common steps are provided. Use `SPACE[n]` for every `padx`/`pady` value and for widget-level `padding` values where practical.
|
|
111
|
+
|
|
112
|
+
### Themes
|
|
113
|
+
|
|
114
|
+
`TkApp` accepts a `theme` parameter:
|
|
115
|
+
|
|
116
|
+
| Value | Meaning |
|
|
117
|
+
|-------|---------|
|
|
118
|
+
| `"kizashi"` (default) | Apply the built-in Kizashi design system |
|
|
119
|
+
| `"none"` | Do not touch ttk styles; use the platform default theme |
|
|
120
|
+
| any built-in name (e.g. `"clam"`, `"vista"`, `"aqua"`) | Switch to that ttk theme without Kizashi overrides |
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
# Use the platform default ttk theme
|
|
124
|
+
app = TkApp(title="Native", theme="none")
|
|
125
|
+
|
|
126
|
+
# Use any installed ttk theme
|
|
127
|
+
app = TkApp(title="Clam", theme="clam")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
See `examples/header_demo.py` for `Layout.header()` / `.status()` chrome and `nextpytk.theme.apply_theme()` usage.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Multiview (Multi-tab)
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from nextpytk import TkApp, Layout
|
|
138
|
+
|
|
139
|
+
app = TkApp(title="Multi-tab App")
|
|
140
|
+
|
|
141
|
+
@app.status("header")
|
|
142
|
+
def header(): return "Common header"
|
|
143
|
+
|
|
144
|
+
with app.view("Tab1", layout=Layout().section("t1_label", "t1_btn")) as v:
|
|
145
|
+
@v.label("t1_label")
|
|
146
|
+
def t1_label(): return "Tab 1 content"
|
|
147
|
+
@v.button("t1_btn", label="Click")
|
|
148
|
+
def t1_btn(vals): return {}
|
|
149
|
+
|
|
150
|
+
with app.view("Tab2", layout=Layout().section("t2_label")) as v:
|
|
151
|
+
@v.label("t2_label")
|
|
152
|
+
def t2_label(): return "Tab 2 content"
|
|
153
|
+
|
|
154
|
+
@app.multiview(
|
|
155
|
+
"main",
|
|
156
|
+
views=["Tab1", "Tab2"],
|
|
157
|
+
toplevel_widgets=("header",),
|
|
158
|
+
initial_state={"tab": "Tab1"},
|
|
159
|
+
on_tab_change=lambda tab: {"tab": tab},
|
|
160
|
+
)
|
|
161
|
+
def main_multiview(): pass
|
|
162
|
+
|
|
163
|
+
app.run(multiview="main")
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
View layouts also accept lists or with-block builders. For example,
|
|
167
|
+
`view_layouts` values may be simple widget-name lists:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
view_layouts = {
|
|
171
|
+
"Home": ["title", "start"],
|
|
172
|
+
"Settings": ["timer", "status"],
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Layout DSL
|
|
179
|
+
|
|
180
|
+
### Simple list
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
app.run(layout=["title", "timer", "start", "status"])
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Each name gets its own pack-based section. Extra kwargs forwarded to `section()`:
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
Layout.from_list(["a", "b"], fill="both", expand=True)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Fluent DSL
|
|
193
|
+
|
|
194
|
+
**Pack sections:**
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
Layout().section("msg").section("phase", "count").section("start", "pause")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Grid builder:**
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from nextpytk.types import Sticky
|
|
204
|
+
|
|
205
|
+
layout = (
|
|
206
|
+
Layout()
|
|
207
|
+
.grid()
|
|
208
|
+
.span(2).widget("title", sticky=Sticky.W)
|
|
209
|
+
.next_row()
|
|
210
|
+
.widget("label", sticky=Sticky.RIGHT).widget("input", sticky=Sticky.LEFT_RIGHT)
|
|
211
|
+
.next_row()
|
|
212
|
+
.span(2).widget("ok")
|
|
213
|
+
.end_grid()
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Grid builder methods:
|
|
218
|
+
|
|
219
|
+
| Method | Description |
|
|
220
|
+
|--------|-------------|
|
|
221
|
+
| `widget(name, *, sticky, padx, pady, colspan, rowspan)` | Place widget at cursor, advance column |
|
|
222
|
+
| `span(n)` | Set colspan for the next `widget()` call |
|
|
223
|
+
| `next_row()` | Move to next row, reset column |
|
|
224
|
+
| `next_col(n)` | Skip n columns |
|
|
225
|
+
| `at(row, col)` | Jump to absolute position |
|
|
226
|
+
| `col_weight(col, w)` | Set a single column weight |
|
|
227
|
+
| `row_weight(row, w)` | Set a single row weight |
|
|
228
|
+
| `col_minsize(col, px)` | Column minimum width |
|
|
229
|
+
| `row_minsize(row, px)` | Row minimum height |
|
|
230
|
+
| `end_grid()` | Return to Layout chain |
|
|
231
|
+
|
|
232
|
+
Use `col_weight(0, 0).col_weight(1, 1)` to set column 0 → weight 0 and column 1 → weight 1. This is explicit and less error-prone than index-position bulk lists.
|
|
233
|
+
|
|
234
|
+
### With-block (context manager)
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from nextpytk import LayoutBuilder
|
|
238
|
+
|
|
239
|
+
# Standalone builder
|
|
240
|
+
builder = LayoutBuilder()
|
|
241
|
+
with builder:
|
|
242
|
+
builder.section("title")
|
|
243
|
+
with builder.grid():
|
|
244
|
+
builder.col_weight(0, 0).col_weight(1, 1)
|
|
245
|
+
builder.widget("celsius", sticky="ew")
|
|
246
|
+
builder.widget("fahrenheit", sticky="ew")
|
|
247
|
+
builder.next_row().span(2).widget("note")
|
|
248
|
+
app.run(layout=builder.build())
|
|
249
|
+
|
|
250
|
+
# Via app.layout() shortcut
|
|
251
|
+
with app.layout() as b:
|
|
252
|
+
b.section("title")
|
|
253
|
+
with b.grid():
|
|
254
|
+
b.col_weight(0, 0).col_weight(1, 1)
|
|
255
|
+
b.widget("celsius", sticky="ew")
|
|
256
|
+
app.run(layout=b.build())
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`with b.grid(...)` auto-closes — no `end_grid()` needed.
|
|
260
|
+
|
|
261
|
+
`grid()` options available directly: `padx`, `pady`, `fill`, `expand`, `uniform`.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Widget Reference
|
|
266
|
+
|
|
267
|
+
| Decorator | Widget | Callback receives | Returns |
|
|
268
|
+
|-----------|--------|-------------------|---------|
|
|
269
|
+
| `@app.label(name, font=..., anchor=..., justify=..., padding=...)` | tk.Label | — | `str` or `dict` |
|
|
270
|
+
| `@app.status(name)` | tk.Label (`role=status` metadata) | — | `str` or `dict` |
|
|
271
|
+
| `@app.message(name, width=..., auto_width=...)` | tk.Label (auto-wrap) | — | `str` or `dict` |
|
|
272
|
+
| `@app.button(name, label=..., font=..., enabled_if=...)` | ttk.Button | entry values `dict` | `dict` |
|
|
273
|
+
| `@app.job(name)` | async callable | entry values `dict` | `dict` |
|
|
274
|
+
| `@app.entry(name, placeholder=..., show=..., font=..., padding=..., width=...)` | ttk.Entry | `str` | `dict` |
|
|
275
|
+
| `@app.checkbutton(name, text=..., font=...)` | ttk.Checkbutton | `bool` | `dict` |
|
|
276
|
+
| `@app.radiobutton(name, text=..., value=..., group=..., font=...)` | ttk.Radiobutton | selected value `str` | `dict` |
|
|
277
|
+
| `@app.combobox(name, values=..., readonly=..., font=...)` | ttk.Combobox | selected value `str` | `dict` |
|
|
278
|
+
| `@app.menubar(name)` | tk.Menu (window menubar) | — | menu item list |
|
|
279
|
+
| `@app.text(name, width=..., height=..., font=...)` | tk.Text | full content `str` | `dict` |
|
|
280
|
+
| `@app.scale(name, from_=..., to=..., orient=...)` | ttk.Scale | value `str` | `dict` |
|
|
281
|
+
| `@app.spinbox(name, from_=..., to=..., values=..., font=...)` | ttk.Spinbox | value `str` | `dict` |
|
|
282
|
+
| `@app.listbox(name, items=..., selectmode=..., font=...)` | tk.Listbox | selected item `str` | `dict` |
|
|
283
|
+
| `@app.canvas(name, width=..., height=...)` | tk.Canvas | — | — |
|
|
284
|
+
|
|
285
|
+
`@app.status` sets schema / accessible `role="status"` metadata. It is **not** an ARIA live region yet (planned for a later release). Prefer it for operation feedback labels; use `@app.label` for static or high-frequency mirror text.
|
|
286
|
+
|
|
287
|
+
`app.run(stages=..., tabposition=...)` and `@app.stages` provide state-driven screen switching (one visible stage at a time). Theme helpers (`apply_theme`, `tokens`, layout chrome) ship in the package root — see `examples/header_demo.py`.
|
|
288
|
+
|
|
289
|
+
Common options:
|
|
290
|
+
- `font`: `(family, size[, weight])` tuple, e.g. `font=("TkDefaultFont", 18, "bold")`
|
|
291
|
+
- `padding`: internal padding; integer or `(x, y)` / `(left, top, right, bottom)` tuple, e.g. `padding=4` or `padding=(4, 2)`
|
|
292
|
+
|
|
293
|
+
Label options: `font`, `anchor`, `justify`, `padding`, `width`.
|
|
294
|
+
|
|
295
|
+
Entry options: `placeholder`, `show`, `font`, `padding`, `width`, `state`.
|
|
296
|
+
- `padding` is the declarative way to increase visual height (ttk.Entry does not support `height`).
|
|
297
|
+
|
|
298
|
+
Button, checkbutton, radiobutton, spinbox, combobox, listbox, text options:
|
|
299
|
+
- `font` is accepted uniformly. Under the hood, ttk widgets use a derived style so the rest of the theme (colors, maps, layout) is inherited.
|
|
300
|
+
|
|
301
|
+
`@app.message` creates an auto-wrapping label. `width` sets initial pixel width; `auto_width=True` (default) tracks parent container resize.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
## Typed Constants
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
from nextpytk.types import Side, Fill, Sticky, State, Orient
|
|
309
|
+
|
|
310
|
+
Layout().section("msg", side=Side.LEFT, fill=Fill.X)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Values use `str` literals compatible with tkinter. `SideLike` / `FillLike` etc.
|
|
314
|
+
accept raw strings too.
|
|
315
|
+
|
|
316
|
+
| Type | Namespace | Example |
|
|
317
|
+
|------|-----------|---------|
|
|
318
|
+
| `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
|
|
319
|
+
| `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
|
|
320
|
+
| `Sticky` | `Sticky.NSEW/LEFT_RIGHT/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
|
|
321
|
+
| `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
|
|
322
|
+
| `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale orientation |
|
|
323
|
+
| `Relief` | `Relief.FLAT/RAISED/SUNKEN/GROOVE/RIDGE/SOLID` | border style |
|
|
324
|
+
| `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
|
|
325
|
+
| `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Schema Export (Agent/LLM)
|
|
330
|
+
|
|
331
|
+
```python
|
|
332
|
+
@label("temperature")
|
|
333
|
+
def t(): return "25°C"
|
|
334
|
+
|
|
335
|
+
app.schema()
|
|
336
|
+
# → {"title": "...", "widgets": [{"name": "temperature", "kind": "label", ...}]}
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
Output is JSON-compatible and can be used as a foundation for agent tools and LLM function definitions.
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## Layout debug
|
|
344
|
+
|
|
345
|
+
After widgets are built, `app.debug_layout()` returns JSON-compatible geometry
|
|
346
|
+
and pack/grid info for every registered widget (useful for clipping, minsize,
|
|
347
|
+
and layout regressions; also handy to hand to an agent).
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
app.run(layout=["msg", "go"]) # or build via tests / custom runner
|
|
351
|
+
print(app.debug_layout())
|
|
352
|
+
# → {"title": "...", "sections": [{"widgets": [{"name": "msg", "geometry": ..., ...}, ...]}]}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
## Async-Native (asyncio + Tkinter)
|
|
358
|
+
|
|
359
|
+
`app.run_async()` runs the app on an asyncio event loop, cooperatively scheduled
|
|
360
|
+
with the Tk main loop via `root.tk.dooneevent(0)`.
|
|
361
|
+
`app.spawn(coro)` schedules async tasks during GUI runtime.
|
|
362
|
+
`@app.job(name)` registers async callables.
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
@app.job("scan")
|
|
366
|
+
async def scan(vals):
|
|
367
|
+
result = await asyncio.to_thread(some_blocking_call)
|
|
368
|
+
return {"status": "done"}
|
|
369
|
+
|
|
370
|
+
app.run_async(layout=Layout().section("status"))
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Examples
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
uv run python examples/grid_temp.py # temperature converter
|
|
377
|
+
uv run python examples/task_panel.py # multi-button panel
|
|
378
|
+
uv run python examples/multiscreen.py # order app with screens
|
|
379
|
+
uv run python examples/widget_gallery.py # all widget types
|
|
380
|
+
uv run python examples/header_demo.py # Layout.header / .status chrome
|
|
381
|
+
uv run python examples/combobox_demo.py # ttk.Combobox
|
|
382
|
+
uv run python examples/menubar_demo.py # menubar
|
|
383
|
+
uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async)
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## Requirements
|
|
389
|
+
|
|
390
|
+
- Python 3.13+ (`requires-python`; examples default to 3.14 via `Makefile` `PYTHON=...`)
|
|
391
|
+
- Tkinter support in your Python build
|
|
392
|
+
- No other dependencies
|
|
393
|
+
|
|
394
|
+
> Note: On some macOS environments, `uv` + `3.14+freethreaded` can fail at Tk startup with `Can't find a usable init.tcl`.
|
|
395
|
+
> You can switch runtimes per command, e.g. `make run PYTHON=3.13`, `make run PYTHON=3.14+freethreaded`, `make run PYTHON=3.15`.
|
|
396
|
+
|
|
397
|
+
---
|
|
398
|
+
|
|
399
|
+
## Related Projects
|
|
400
|
+
|
|
401
|
+
- **`tkinter` (stdlib)**: nextpytk builds on top — adding Decorator / Schema / A11y layers.
|
|
402
|
+
- **`ttk`**: Native look and accessibility; nextpytk prefers ttk widgets where available.
|
|
403
|
+
- **`CustomTkinter`**: Modern look via Canvas rendering. nextpytk takes the opposite approach: use native widgets and embed A11y from the start.
|
|
404
|
+
- **`TkRouter`** (israel-dryer, author of ttkbootstrap): Declarative view routing with URL-style paths, animated transitions, and history stack. Complements nextpytk's `multiview` — routing vs widget composition.
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
MIT
|
|
409
|
+
|
|
410
|
+
## Author
|
|
411
|
+
|
|
412
|
+
Takuya Nishimoto — Shuaruta Inc.
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Requires-Python: >=3.13
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
|
|
12
|
-
# nextpytk — Flask-style Decorator API for Tkinter
|
|
13
|
-
|
|
14
|
-
nextpytk wraps Tkinter in Python decorators, inspired by Flask.
|
|
15
|
-
Widget registration and layout are decoupled via dependency injection.
|
|
16
|
-
All widgets expose a JSON schema for AI/LLM consumption.
|
|
17
|
-
Uses ttk widgets where available (Button, Entry, Checkbutton, Radiobutton, Scale, Spinbox, Notebook).
|
|
1
|
+
# nextpytk
|
|
2
|
+
|
|
3
|
+
Accessible, declarative Tkinter applications from ordinary Python functions.
|
|
4
|
+
|
|
5
|
+
Register widgets as plain Python functions, declare layout separately, and
|
|
6
|
+
keep roles/descriptions in one place. The decorator style is Flask-inspired;
|
|
7
|
+
`schema()` exports the same structure for agents and tools.
|
|
8
|
+
Uses ttk widgets where available.
|
|
18
9
|
|
|
19
10
|
---
|
|
20
11
|
|
|
@@ -54,6 +45,68 @@ app.run(layout=b.build())
|
|
|
54
45
|
|
|
55
46
|
---
|
|
56
47
|
|
|
48
|
+
## Design System
|
|
49
|
+
|
|
50
|
+
nextpytk ships with typed constants and design tokens. Prefer them over raw tkinter strings and magic numbers so every app stays consistent and IDE completion works out of the box.
|
|
51
|
+
|
|
52
|
+
### Typed Constants
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from nextpytk.types import Side, Fill, Sticky, State, Orient
|
|
56
|
+
|
|
57
|
+
Layout().section("msg", side=Side.LEFT, fill=Fill.X)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Type | Namespace | Example |
|
|
61
|
+
|------|-----------|---------|
|
|
62
|
+
| `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
|
|
63
|
+
| `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
|
|
64
|
+
| `Sticky` | `Sticky.NSEW/EW/NS/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
|
|
65
|
+
| `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
|
|
66
|
+
| `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale / paned orientation |
|
|
67
|
+
| `Relief` | `Relief.FLAT/RAISED/SUNKEN/...` | border style |
|
|
68
|
+
| `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
|
|
69
|
+
| `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
|
|
70
|
+
|
|
71
|
+
Each type has a matching `*Like` literal alias (e.g. `FillLike`), so raw strings still work when needed.
|
|
72
|
+
|
|
73
|
+
### Spacing tokens
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from nextpytk.tokens import SPACE
|
|
77
|
+
|
|
78
|
+
SPACE[1] # 4px — small padding inside a widget
|
|
79
|
+
SPACE[2] # 8px — adjacent widgets
|
|
80
|
+
SPACE[3] # 12px — section inner gaps
|
|
81
|
+
SPACE[4] # 16px — section margins
|
|
82
|
+
SPACE[6] # 24px — large blocks
|
|
83
|
+
SPACE[8] # 32px — page-level gaps
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The scale is based on a 4px unit and only common steps are provided. Use `SPACE[n]` for every `padx`/`pady` value and for widget-level `padding` values where practical.
|
|
87
|
+
|
|
88
|
+
### Themes
|
|
89
|
+
|
|
90
|
+
`TkApp` accepts a `theme` parameter:
|
|
91
|
+
|
|
92
|
+
| Value | Meaning |
|
|
93
|
+
|-------|---------|
|
|
94
|
+
| `"kizashi"` (default) | Apply the built-in Kizashi design system |
|
|
95
|
+
| `"none"` | Do not touch ttk styles; use the platform default theme |
|
|
96
|
+
| any built-in name (e.g. `"clam"`, `"vista"`, `"aqua"`) | Switch to that ttk theme without Kizashi overrides |
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
# Use the platform default ttk theme
|
|
100
|
+
app = TkApp(title="Native", theme="none")
|
|
101
|
+
|
|
102
|
+
# Use any installed ttk theme
|
|
103
|
+
app = TkApp(title="Clam", theme="clam")
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
See `examples/header_demo.py` for `Layout.header()` / `.status()` chrome and `nextpytk.theme.apply_theme()` usage.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
57
110
|
## Multiview (Multi-tab)
|
|
58
111
|
|
|
59
112
|
```python
|
|
@@ -86,11 +139,14 @@ def main_multiview(): pass
|
|
|
86
139
|
app.run(multiview="main")
|
|
87
140
|
```
|
|
88
141
|
|
|
89
|
-
View layouts also accept lists or with-block builders
|
|
142
|
+
View layouts also accept lists or with-block builders. For example,
|
|
143
|
+
`view_layouts` values may be simple widget-name lists:
|
|
90
144
|
|
|
91
145
|
```python
|
|
92
|
-
|
|
93
|
-
|
|
146
|
+
view_layouts = {
|
|
147
|
+
"Home": ["title", "start"],
|
|
148
|
+
"Settings": ["timer", "status"],
|
|
149
|
+
}
|
|
94
150
|
```
|
|
95
151
|
|
|
96
152
|
---
|
|
@@ -122,13 +178,16 @@ Layout().section("msg").section("phase", "count").section("start", "pause")
|
|
|
122
178
|
```python
|
|
123
179
|
from nextpytk.types import Sticky
|
|
124
180
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.
|
|
181
|
+
layout = (
|
|
182
|
+
Layout()
|
|
183
|
+
.grid()
|
|
184
|
+
.span(2).widget("title", sticky=Sticky.W)
|
|
185
|
+
.next_row()
|
|
186
|
+
.widget("label", sticky=Sticky.RIGHT).widget("input", sticky=Sticky.LEFT_RIGHT)
|
|
187
|
+
.next_row()
|
|
188
|
+
.span(2).widget("ok")
|
|
189
|
+
.end_grid()
|
|
190
|
+
)
|
|
132
191
|
```
|
|
133
192
|
|
|
134
193
|
Grid builder methods:
|
|
@@ -140,15 +199,13 @@ Grid builder methods:
|
|
|
140
199
|
| `next_row()` | Move to next row, reset column |
|
|
141
200
|
| `next_col(n)` | Skip n columns |
|
|
142
201
|
| `at(row, col)` | Jump to absolute position |
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `col_weight(col, w)` | Single column weight |
|
|
146
|
-
| `row_weight(row, w)` | Single row weight |
|
|
202
|
+
| `col_weight(col, w)` | Set a single column weight |
|
|
203
|
+
| `row_weight(row, w)` | Set a single row weight |
|
|
147
204
|
| `col_minsize(col, px)` | Column minimum width |
|
|
148
205
|
| `row_minsize(row, px)` | Row minimum height |
|
|
149
206
|
| `end_grid()` | Return to Layout chain |
|
|
150
207
|
|
|
151
|
-
`
|
|
208
|
+
Use `col_weight(0, 0).col_weight(1, 1)` to set column 0 → weight 0 and column 1 → weight 1. This is explicit and less error-prone than index-position bulk lists.
|
|
152
209
|
|
|
153
210
|
### With-block (context manager)
|
|
154
211
|
|
|
@@ -159,7 +216,8 @@ from nextpytk import LayoutBuilder
|
|
|
159
216
|
builder = LayoutBuilder()
|
|
160
217
|
with builder:
|
|
161
218
|
builder.section("title")
|
|
162
|
-
with builder.grid(
|
|
219
|
+
with builder.grid():
|
|
220
|
+
builder.col_weight(0, 0).col_weight(1, 1)
|
|
163
221
|
builder.widget("celsius", sticky="ew")
|
|
164
222
|
builder.widget("fahrenheit", sticky="ew")
|
|
165
223
|
builder.next_row().span(2).widget("note")
|
|
@@ -168,14 +226,15 @@ app.run(layout=builder.build())
|
|
|
168
226
|
# Via app.layout() shortcut
|
|
169
227
|
with app.layout() as b:
|
|
170
228
|
b.section("title")
|
|
171
|
-
with b.grid(
|
|
229
|
+
with b.grid():
|
|
230
|
+
b.col_weight(0, 0).col_weight(1, 1)
|
|
172
231
|
b.widget("celsius", sticky="ew")
|
|
173
232
|
app.run(layout=b.build())
|
|
174
233
|
```
|
|
175
234
|
|
|
176
235
|
`with b.grid(...)` auto-closes — no `end_grid()` needed.
|
|
177
236
|
|
|
178
|
-
`grid()` options available directly: `
|
|
237
|
+
`grid()` options available directly: `padx`, `pady`, `fill`, `expand`, `uniform`.
|
|
179
238
|
|
|
180
239
|
---
|
|
181
240
|
|
|
@@ -184,24 +243,36 @@ app.run(layout=b.build())
|
|
|
184
243
|
| Decorator | Widget | Callback receives | Returns |
|
|
185
244
|
|-----------|--------|-------------------|---------|
|
|
186
245
|
| `@app.label(name, font=..., anchor=..., justify=..., padding=...)` | tk.Label | — | `str` or `dict` |
|
|
187
|
-
| `@app.status(name)` | tk.Label (role=status) | — | `str` or `dict` |
|
|
246
|
+
| `@app.status(name)` | tk.Label (`role=status` metadata) | — | `str` or `dict` |
|
|
188
247
|
| `@app.message(name, width=..., auto_width=...)` | tk.Label (auto-wrap) | — | `str` or `dict` |
|
|
189
|
-
| `@app.button(name, label=..., enabled_if=...)` | ttk.Button | entry values `dict` | `dict` |
|
|
248
|
+
| `@app.button(name, label=..., font=..., enabled_if=...)` | ttk.Button | entry values `dict` | `dict` |
|
|
190
249
|
| `@app.job(name)` | async callable | entry values `dict` | `dict` |
|
|
191
|
-
| `@app.entry(name, placeholder=..., show=...)` | ttk.Entry | `str` | `dict` |
|
|
192
|
-
| `@app.checkbutton(name, text=...)` | ttk.Checkbutton | `bool` | `dict` |
|
|
193
|
-
| `@app.radiobutton(name, text=..., value=..., group=...)` | ttk.Radiobutton | selected value `str` | `dict` |
|
|
194
|
-
| `@app.
|
|
250
|
+
| `@app.entry(name, placeholder=..., show=..., font=..., padding=..., width=...)` | ttk.Entry | `str` | `dict` |
|
|
251
|
+
| `@app.checkbutton(name, text=..., font=...)` | ttk.Checkbutton | `bool` | `dict` |
|
|
252
|
+
| `@app.radiobutton(name, text=..., value=..., group=..., font=...)` | ttk.Radiobutton | selected value `str` | `dict` |
|
|
253
|
+
| `@app.combobox(name, values=..., readonly=..., font=...)` | ttk.Combobox | selected value `str` | `dict` |
|
|
254
|
+
| `@app.menubar(name)` | tk.Menu (window menubar) | — | menu item list |
|
|
255
|
+
| `@app.text(name, width=..., height=..., font=...)` | tk.Text | full content `str` | `dict` |
|
|
195
256
|
| `@app.scale(name, from_=..., to=..., orient=...)` | ttk.Scale | value `str` | `dict` |
|
|
196
|
-
| `@app.spinbox(name, from_=..., to=..., values=...)` | ttk.Spinbox | value `str` | `dict` |
|
|
197
|
-
| `@app.listbox(name, items=..., selectmode=...)` | tk.Listbox | selected item `str` | `dict` |
|
|
257
|
+
| `@app.spinbox(name, from_=..., to=..., values=..., font=...)` | ttk.Spinbox | value `str` | `dict` |
|
|
258
|
+
| `@app.listbox(name, items=..., selectmode=..., font=...)` | tk.Listbox | selected item `str` | `dict` |
|
|
198
259
|
| `@app.canvas(name, width=..., height=...)` | tk.Canvas | — | — |
|
|
199
260
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
261
|
+
`@app.status` sets schema / accessible `role="status"` metadata. It is **not** an ARIA live region yet (planned for a later release). Prefer it for operation feedback labels; use `@app.label` for static or high-frequency mirror text.
|
|
262
|
+
|
|
263
|
+
`app.run(stages=..., tabposition=...)` and `@app.stages` provide state-driven screen switching (one visible stage at a time). Theme helpers (`apply_theme`, `tokens`, layout chrome) ship in the package root — see `examples/header_demo.py`.
|
|
264
|
+
|
|
265
|
+
Common options:
|
|
266
|
+
- `font`: `(family, size[, weight])` tuple, e.g. `font=("TkDefaultFont", 18, "bold")`
|
|
267
|
+
- `padding`: internal padding; integer or `(x, y)` / `(left, top, right, bottom)` tuple, e.g. `padding=4` or `padding=(4, 2)`
|
|
268
|
+
|
|
269
|
+
Label options: `font`, `anchor`, `justify`, `padding`, `width`.
|
|
270
|
+
|
|
271
|
+
Entry options: `placeholder`, `show`, `font`, `padding`, `width`, `state`.
|
|
272
|
+
- `padding` is the declarative way to increase visual height (ttk.Entry does not support `height`).
|
|
273
|
+
|
|
274
|
+
Button, checkbutton, radiobutton, spinbox, combobox, listbox, text options:
|
|
275
|
+
- `font` is accepted uniformly. Under the hood, ttk widgets use a derived style so the rest of the theme (colors, maps, layout) is inherited.
|
|
205
276
|
|
|
206
277
|
`@app.message` creates an auto-wrapping label. `width` sets initial pixel width; `auto_width=True` (default) tracks parent container resize.
|
|
207
278
|
|
|
@@ -241,7 +312,21 @@ app.schema()
|
|
|
241
312
|
# → {"title": "...", "widgets": [{"name": "temperature", "kind": "label", ...}]}
|
|
242
313
|
```
|
|
243
314
|
|
|
244
|
-
Output is JSON-compatible and can
|
|
315
|
+
Output is JSON-compatible and can be used as a foundation for agent tools and LLM function definitions.
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Layout debug
|
|
320
|
+
|
|
321
|
+
After widgets are built, `app.debug_layout()` returns JSON-compatible geometry
|
|
322
|
+
and pack/grid info for every registered widget (useful for clipping, minsize,
|
|
323
|
+
and layout regressions; also handy to hand to an agent).
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
app.run(layout=["msg", "go"]) # or build via tests / custom runner
|
|
327
|
+
print(app.debug_layout())
|
|
328
|
+
# → {"title": "...", "sections": [{"widgets": [{"name": "msg", "geometry": ..., ...}, ...]}]}
|
|
329
|
+
```
|
|
245
330
|
|
|
246
331
|
---
|
|
247
332
|
|
|
@@ -268,7 +353,9 @@ uv run python examples/grid_temp.py # temperature converter
|
|
|
268
353
|
uv run python examples/task_panel.py # multi-button panel
|
|
269
354
|
uv run python examples/multiscreen.py # order app with screens
|
|
270
355
|
uv run python examples/widget_gallery.py # all widget types
|
|
271
|
-
uv run python examples/
|
|
356
|
+
uv run python examples/header_demo.py # Layout.header / .status chrome
|
|
357
|
+
uv run python examples/combobox_demo.py # ttk.Combobox
|
|
358
|
+
uv run python examples/menubar_demo.py # menubar
|
|
272
359
|
uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async)
|
|
273
360
|
```
|
|
274
361
|
|
|
@@ -276,7 +363,7 @@ uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async
|
|
|
276
363
|
|
|
277
364
|
## Requirements
|
|
278
365
|
|
|
279
|
-
- Python 3.
|
|
366
|
+
- Python 3.13+ (`requires-python`; examples default to 3.14 via `Makefile` `PYTHON=...`)
|
|
280
367
|
- Tkinter support in your Python build
|
|
281
368
|
- No other dependencies
|
|
282
369
|
|