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.
@@ -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
- Metadata-Version: 2.4
2
- Name: nextpytk
3
- Version: 0.3.0
4
- Summary: Flask-style decorator API for tkinter GUI: schema-driven, A11y-first, agent-compatible.
5
- Project-URL: Homepage, https://github.com/nishimotz/nextpytk
6
- Author: Takuya Nishimoto
7
- License: MIT
8
- License-File: LICENSE
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
- @app.multiview("main", views=["Home", "Settings"],
93
- view_layouts={"Home": ["title", "start"], "Settings": ["timer", "status"]})
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
- Layout().grid()
126
- .span(2).widget("title", sticky=Sticky.W)
127
- .next_row()
128
- .widget("label", sticky=Sticky.RIGHT).widget("input", sticky=Sticky.LEFT_RIGHT)
129
- .next_row()
130
- .span(2).widget("ok")
131
- .end_grid()
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
- | `col_weights(*w)` | Bulk column weights: `col_weights(0, 1, 1)` |
144
- | `row_weights(*w)` | Bulk row weights |
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
- `col_weights(0, 1, 1)` means column 0 → weight 0, column 1 → weight 1, column 2 → weight 1.
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(col_weights=(0, 1)):
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(col_weights=(0, 1)):
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: `col_weights=(0,1)`, `row_weights=(...)`, `padx`, `pady`, `fill`, `expand`, `uniform`.
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.text(name, width=..., height=...)` | tk.Text | full content `str` | `dict` |
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
- Label options:
201
- - `font`: e.g. `font=("TkDefaultFont", 18, "bold")`
202
- - `anchor`: e.g. `anchor="e"` (right-aligned)
203
- - `justify`: multi-line alignment, e.g. `justify="right"`
204
- - `padding`: e.g. `padding=4` or `padding=(4, 2)`
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 serve as LLM Function Calling definitions.
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/disk_usage_flat_viewer.py # ncdu-style viewer (sync)
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.14 by default (`PYTHON=...` override supported)
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