nextpytk 0.4.0__tar.gz → 0.4.2__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,628 @@
1
+ Metadata-Version: 2.4
2
+ Name: nextpytk
3
+ Version: 0.4.2
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 registered widget structure as JSON-compatible data.
32
+ Uses ttk widgets where available.
33
+
34
+ ---
35
+
36
+ ## Quick Start
37
+
38
+ Start with an app where a button updates a message.
39
+
40
+ ```bash
41
+ pip install nextpytk
42
+ ```
43
+
44
+ ```python
45
+ from nextpytk import TkApp
46
+
47
+ app = TkApp(title="Hello")
48
+
49
+ @app.status("msg")
50
+ def msg():
51
+ return "Hello, world!"
52
+
53
+ @app.button("greet", label="Greet")
54
+ def on_greet():
55
+ return {"msg": "Button clicked!"}
56
+
57
+ app.run(layout=["msg", "greet"])
58
+ ```
59
+
60
+ ---
61
+
62
+ ## How it works
63
+
64
+ In nextpytk you register widgets by name and declare layout separately.
65
+
66
+ This example registers two widgets: `msg` and `greet`.
67
+
68
+ ```python
69
+ @app.status("msg")
70
+ ```
71
+
72
+ `msg` is a status area that shows a message.
73
+
74
+ ```python
75
+ @app.button("greet", label="Greet")
76
+ ```
77
+
78
+ `greet` is a button labeled "Greet".
79
+
80
+ Finally, name the display order:
81
+
82
+ ```python
83
+ app.run(layout=["msg", "greet"])
84
+ ```
85
+
86
+ When you press the button, the callback returns this `dict`:
87
+
88
+ ```python
89
+ {"msg": "Button clicked!"}
90
+ ```
91
+
92
+ nextpytk merges that into the app `state`.
93
+
94
+ Because `msg` is the name of the registered status area, its text becomes
95
+ "Button clicked!".
96
+
97
+ So the basic flow is:
98
+
99
+ * Register widgets by name
100
+ * List those names in the layout
101
+ * Return a `dict` from a callback
102
+ * `state` updates and matching widgets refresh
103
+
104
+ ---
105
+
106
+ ## Using input values
107
+
108
+ Next, change the app so you type a name and press Greet.
109
+
110
+ ```python
111
+ from nextpytk import TkApp
112
+
113
+ app = TkApp(title="Hello")
114
+
115
+ @app.entry("name", placeholder="Name")
116
+ def on_name():
117
+ return {}
118
+
119
+ @app.status("msg")
120
+ def msg():
121
+ return "Enter your name"
122
+
123
+ @app.button("greet", label="Greet")
124
+ def on_greet(values):
125
+ name = values["name"]
126
+ return {"msg": f"Hello, {name}!"}
127
+
128
+ app.run(layout=["name", "greet", "msg"])
129
+ ```
130
+
131
+ `entry` registers a text field.
132
+
133
+ ```python
134
+ @app.entry("name", placeholder="Name")
135
+ ```
136
+
137
+ Again, `"name"` is the widget name. An on-change callback is required; if you
138
+ only read the field from a button, a no-arg callback that returns `{}` is enough.
139
+
140
+ In the button callback, read the current field value from `values`:
141
+
142
+ ```python
143
+ def on_greet(values):
144
+ name = values["name"]
145
+ ```
146
+
147
+ `values["name"]` is the current value of the `entry` registered as `name`.
148
+
149
+ For example, if you type
150
+
151
+ ```text
152
+ Taro
153
+ ```
154
+
155
+ and press the button, the callback returns:
156
+
157
+ ```python
158
+ {"msg": "Hello, Taro!"}
159
+ ```
160
+
161
+ That dict merges into `state` and updates the status area named `msg`.
162
+
163
+ ## `values` and `state`
164
+
165
+ Two dictionaries appear here:
166
+
167
+ * `values`
168
+ Input values at the moment the callback runs
169
+
170
+ * `state`
171
+ Shared current state for the whole app
172
+
173
+ A button callback reads `values`, then returns a `dict` of state changes:
174
+
175
+ ```python
176
+ def on_greet(values):
177
+ name = values["name"]
178
+ return {"msg": f"Hello, {name}!"}
179
+ ```
180
+
181
+ The flow is:
182
+
183
+ * Type into an `entry`
184
+ * Press the button
185
+ * Read input from `values`
186
+ * Return updates as a `dict`
187
+ * Those updates merge into `state`
188
+ * Matching widgets refresh from `state`
189
+
190
+ That is nextpytk's basic state-update model.
191
+
192
+ ---
193
+
194
+ ## Design System
195
+
196
+ 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.
197
+
198
+ ### Typed Constants
199
+
200
+ ```python
201
+ from nextpytk.types import Side, Fill, Sticky, State, Orient
202
+
203
+ Layout().section("msg", side=Side.LEFT, fill=Fill.X)
204
+ ```
205
+
206
+ | Type | Namespace | Example |
207
+ |------|-----------|---------|
208
+ | `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
209
+ | `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
210
+ | `Sticky` | `Sticky.NSEW/EW/NS/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
211
+ | `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
212
+ | `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale / paned orientation |
213
+ | `Relief` | `Relief.FLAT/RAISED/SUNKEN/...` | border style |
214
+ | `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
215
+ | `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
216
+ | `EventSeq` | `EventSeq.RETURN/ESCAPE/BACKSPACE/DELETE/TAB/...` | event sequences for bindings |
217
+ | `EventSeq` (mouse) | `EventSeq.BUTTON_1/2/3`, `DOUBLE_BUTTON_1/2/3`, `PRIMARY_DOUBLE_CLICK` | mouse event sequences |
218
+ | `EventSeq` (virtual) | `EventSeq.LISTBOX_SELECT/COMBOBOX_SELECTED/NOTEBOOK_TAB_CHANGED/...` | virtual events |
219
+
220
+ Each type has a matching `*Like` literal alias (e.g. `FillLike`), so raw strings still work when needed.
221
+
222
+ ### Event sequences
223
+
224
+ Use `EventSeq` constants for widget-level event bindings (`events=` on
225
+ `@app.listbox` and `@app.entry`). These handlers are **separate from** the
226
+ widget's select/change callback:
227
+
228
+ | API | Handler argument | Typical use |
229
+ |-----|------------------|-------------|
230
+ | `@app.listbox` select callback | selected index `int` (`-1` if none) | react to selection |
231
+ | `@app.listbox(..., events=...)` | current **state** dict | Return / double-click / Backspace |
232
+ | `@app.entry` change callback | value `str` | react to typing |
233
+ | `@app.entry(..., events=...)` | **entry values** dict (all entries) | Return to submit, like a button |
234
+
235
+ ```python
236
+ from nextpytk.types import EventSeq, SelectMode
237
+
238
+ @app.listbox(
239
+ "results",
240
+ items_key="results_items",
241
+ selectmode=SelectMode.BROWSE,
242
+ events={
243
+ EventSeq.RETURN: lambda state: open_child(),
244
+ EventSeq.PRIMARY_DOUBLE_CLICK: lambda state: open_child(),
245
+ EventSeq.BACKSPACE: lambda state: go_parent(),
246
+ },
247
+ )
248
+ def on_results_select(idx: int) -> dict:
249
+ return {"status": f"selected:{idx}"}
250
+
251
+ @app.entry(
252
+ "query",
253
+ events={
254
+ EventSeq.RETURN: lambda values: {
255
+ "status": f"search:{(values.get('query') or '').strip()}"
256
+ },
257
+ },
258
+ )
259
+ def on_query(value: str) -> dict:
260
+ return {}
261
+ ```
262
+
263
+ `EventSeq.PRIMARY_CLICK`, `EventSeq.PRIMARY_DOUBLE_CLICK`, and
264
+ `EventSeq.PRIMARY_BUTTON_RELEASE` are a11y-aware lazy descriptors that
265
+ return the event sequence for the OS-configured primary mouse button
266
+ (accounting for left/right button swap on Windows, macOS, and Linux/GNOME).
267
+
268
+ ### Dynamic choices
269
+
270
+ State-driven choices mirror treeview's `rows_key`: keep the selection in
271
+ `state[name]` / `state[key]`, and refresh the options via a separate key.
272
+
273
+ ```python
274
+ @app.listbox("results", items_key="results_items")
275
+ def on_results_select(idx: int) -> dict:
276
+ return {}
277
+
278
+ @app.combobox("folder", values_key="folder_values")
279
+ def on_folder(value: str) -> dict:
280
+ return {}
281
+
282
+ # Later (button, job, or initial_state):
283
+ app.apply_state({
284
+ "results_items": ["a", "b", "c"],
285
+ "folder_values": ["INBOX", "Sent"],
286
+ })
287
+ ```
288
+
289
+ Omit `items_key` / `values_key` to keep a static `items=` / `values=` list.
290
+ ### Spacing tokens
291
+
292
+ ```python
293
+ from nextpytk.tokens import SPACE
294
+
295
+ SPACE[1] # 4px — small padding inside a widget
296
+ SPACE[2] # 8px — adjacent widgets
297
+ SPACE[3] # 12px — section inner gaps
298
+ SPACE[4] # 16px — section margins
299
+ SPACE[6] # 24px — large blocks
300
+ SPACE[8] # 32px — page-level gaps
301
+ ```
302
+
303
+ 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.
304
+
305
+ ### Themes
306
+
307
+ `TkApp` accepts a `theme` parameter:
308
+
309
+ | Value | Meaning |
310
+ |-------|---------|
311
+ | `"kizashi"` (default) | Apply the built-in Kizashi design system |
312
+ | `"none"` | Do not touch ttk styles; use the platform default theme |
313
+ | any built-in name (e.g. `"clam"`, `"vista"`, `"aqua"`) | Switch to that ttk theme without Kizashi overrides |
314
+
315
+ ```python
316
+ # Use the platform default ttk theme
317
+ app = TkApp(title="Native", theme="none")
318
+
319
+ # Use any installed ttk theme
320
+ app = TkApp(title="Clam", theme="clam")
321
+ ```
322
+
323
+ See `examples/header_demo.py` for `Layout.header()` / `.status()` chrome and `nextpytk.theme.apply_theme()` usage.
324
+
325
+ ---
326
+
327
+ ## Multiview (Multi-tab)
328
+
329
+ ```python
330
+ from nextpytk import TkApp, Layout
331
+
332
+ app = TkApp(title="Multi-tab App")
333
+
334
+ @app.status("header")
335
+ def header(): return "Common header"
336
+
337
+ with app.view("Tab1", layout=Layout().section("t1_label", "t1_btn")) as v:
338
+ @v.label("t1_label")
339
+ def t1_label(): return "Tab 1 content"
340
+ @v.button("t1_btn", label="Click")
341
+ def t1_btn(vals): return {}
342
+
343
+ with app.view("Tab2", layout=Layout().section("t2_label")) as v:
344
+ @v.label("t2_label")
345
+ def t2_label(): return "Tab 2 content"
346
+
347
+ @app.multiview(
348
+ "main",
349
+ views=["Tab1", "Tab2"],
350
+ toplevel_widgets=("header",),
351
+ initial_state={"tab": "Tab1"},
352
+ on_tab_change=lambda tab: {"tab": tab},
353
+ )
354
+ def main_multiview(): pass
355
+
356
+ app.run(multiview="main")
357
+ ```
358
+
359
+ View layouts also accept lists or with-block builders. For example,
360
+ `view_layouts` values may be simple widget-name lists:
361
+
362
+ ```python
363
+ view_layouts = {
364
+ "Home": ["title", "start"],
365
+ "Settings": ["timer", "status"],
366
+ }
367
+ ```
368
+
369
+ ---
370
+
371
+ ## Layout DSL
372
+
373
+ Three styles — pick the one that fits:
374
+
375
+ ```python
376
+ from nextpytk import Layout
377
+
378
+ # 1) Simple list (easiest)
379
+ app.run(layout=["msg", "greet"])
380
+
381
+ # 2) Fluent DSL
382
+ app.run(layout=Layout().section("msg").section("greet"))
383
+
384
+ # 3) with-block (context manager)
385
+ with app.layout() as b:
386
+ b.section("msg")
387
+ b.section("greet")
388
+ app.run(layout=b.build())
389
+ ```
390
+
391
+ ### Simple list
392
+
393
+ ```python
394
+ app.run(layout=["title", "timer", "start", "status"])
395
+ ```
396
+
397
+ Each name gets its own pack-based section. Extra kwargs forwarded to `section()`:
398
+
399
+ ```python
400
+ Layout.from_list(["a", "b"], fill="both", expand=True)
401
+ ```
402
+
403
+ ### Fluent DSL
404
+
405
+ **Pack sections:**
406
+
407
+ ```python
408
+ Layout().section("msg").section("phase", "count").section("start", "pause")
409
+ ```
410
+
411
+ **Grid builder:**
412
+
413
+ ```python
414
+ from nextpytk.types import Sticky
415
+
416
+ layout = (
417
+ Layout()
418
+ .grid()
419
+ .span(2).widget("title", sticky=Sticky.W)
420
+ .next_row()
421
+ .widget("label", sticky=Sticky.RIGHT).widget("input", sticky=Sticky.LEFT_RIGHT)
422
+ .next_row()
423
+ .span(2).widget("ok")
424
+ .end_grid()
425
+ )
426
+ ```
427
+
428
+ Grid builder methods:
429
+
430
+ | Method | Description |
431
+ |--------|-------------|
432
+ | `widget(name, *, sticky, padx, pady, colspan, rowspan)` | Place widget at cursor, advance column |
433
+ | `span(n)` | Set colspan for the next `widget()` call |
434
+ | `next_row()` | Move to next row, reset column |
435
+ | `next_col(n)` | Skip n columns |
436
+ | `at(row, col)` | Jump to absolute position |
437
+ | `col_weight(col, w)` | Set a single column weight |
438
+ | `row_weight(row, w)` | Set a single row weight |
439
+ | `col_minsize(col, px)` | Column minimum width |
440
+ | `row_minsize(row, px)` | Row minimum height |
441
+ | `end_grid()` | Return to Layout chain |
442
+
443
+ Use `col_weight(0, 0).col_weight(1, 1)` to set column 0 → weight 0 and column 1 → weight 1.
444
+
445
+ ### With-block (context manager)
446
+
447
+ ```python
448
+ from nextpytk import LayoutBuilder
449
+
450
+ # Standalone builder
451
+ builder = LayoutBuilder()
452
+ with builder:
453
+ builder.section("title")
454
+ with builder.grid():
455
+ builder.col_weight(0, 0).col_weight(1, 1)
456
+ builder.widget("celsius", sticky="ew")
457
+ builder.widget("fahrenheit", sticky="ew")
458
+ builder.next_row().span(2).widget("note")
459
+ app.run(layout=builder.build())
460
+
461
+ # Via app.layout() shortcut
462
+ with app.layout() as b:
463
+ b.section("title")
464
+ with b.grid():
465
+ b.col_weight(0, 0).col_weight(1, 1)
466
+ b.widget("celsius", sticky="ew")
467
+ app.run(layout=b.build())
468
+ ```
469
+
470
+ `with b.grid(...)` auto-closes — no `end_grid()` needed.
471
+
472
+ `grid()` options available directly: `padx`, `pady`, `fill`, `expand`, `uniform`.
473
+
474
+ ---
475
+
476
+ ## Widget Reference
477
+
478
+ | Decorator | Widget | Callback receives | Returns |
479
+ |-----------|--------|-------------------|---------|
480
+ | `@app.label(name, font=..., anchor=..., justify=..., padding=...)` | tk.Label | — | `str` or `dict` |
481
+ | `@app.status(name)` | tk.Label (`role=status` metadata) | — | `str` or `dict` |
482
+ | `@app.message(name, width=..., auto_width=...)` | tk.Label (auto-wrap) | — | `str` or `dict` |
483
+ | `@app.button(name, label=..., font=..., enabled_if=...)` | ttk.Button | entry values `dict` | `dict` |
484
+ | `@app.job(name)` | async callable | entry values `dict` | `dict` |
485
+ | `@app.entry(name, placeholder=..., show=..., font=..., padding=..., width=..., events=...)` | ttk.Entry | `str` | `dict` |
486
+ | `@app.checkbutton(name, text=..., font=...)` | ttk.Checkbutton | `bool` | `dict` |
487
+ | `@app.radiobutton(name, text=..., value=..., group=..., font=...)` | ttk.Radiobutton | selected value `str` | `dict` |
488
+ | `@app.combobox(name, values=..., values_key=..., readonly=..., font=...)` | ttk.Combobox | selected value `str` | `dict` |
489
+ | `@app.menubar(name)` | tk.Menu (window menubar) | — | menu item list |
490
+ | `@app.text(name, width=..., height=..., font=...)` | tk.Text | full content `str` | `dict` |
491
+ | `@app.scale(name, from_=..., to=..., orient=...)` | ttk.Scale | value `str` | `dict` |
492
+ | `@app.spinbox(name, from_=..., to=..., values=..., font=...)` | ttk.Spinbox | value `str` | `dict` |
493
+ | `@app.listbox(name, items=..., items_key=..., selectmode=..., font=..., events=...)` | tk.Listbox | selected index `int` (`-1` if none) | `dict` |
494
+ | `@app.canvas(name, width=..., height=...)` | tk.Canvas | — | — |
495
+
496
+ `@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.
497
+
498
+ `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`.
499
+
500
+ Common options:
501
+ - `font`: `(family, size[, weight])` tuple, e.g. `font=("TkDefaultFont", 18, "bold")`
502
+ - `padding`: internal padding; integer or `(x, y)` / `(left, top, right, bottom)` tuple, e.g. `padding=4` or `padding=(4, 2)`
503
+
504
+ Label options: `font`, `anchor`, `justify`, `padding`, `width`.
505
+
506
+ Entry options: `placeholder`, `show`, `font`, `padding`, `width`, `state`.
507
+ - `padding` is the declarative way to increase visual height (ttk.Entry does not support `height`).
508
+
509
+ Button, checkbutton, radiobutton, spinbox, combobox, listbox, text options:
510
+ - `font` is accepted uniformly. Under the hood, ttk widgets use a derived style so the rest of the theme (colors, maps, layout) is inherited.
511
+
512
+ `@app.message` creates an auto-wrapping label. `width` sets initial pixel width; `auto_width=True` (default) tracks parent container resize.
513
+
514
+ ---
515
+
516
+ ## Typed Constants
517
+
518
+ ```python
519
+ from nextpytk.types import Side, Fill, Sticky, State, Orient
520
+
521
+ Layout().section("msg", side=Side.LEFT, fill=Fill.X)
522
+ ```
523
+
524
+ Values use `str` literals compatible with tkinter. `SideLike` / `FillLike` etc.
525
+ accept raw strings too.
526
+
527
+ | Type | Namespace | Example |
528
+ |------|-----------|---------|
529
+ | `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
530
+ | `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
531
+ | `Sticky` | `Sticky.NSEW/LEFT_RIGHT/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
532
+ | `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
533
+ | `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale orientation |
534
+ | `Relief` | `Relief.FLAT/RAISED/SUNKEN/GROOVE/RIDGE/SOLID` | border style |
535
+ | `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
536
+ | `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
537
+ | `EventSeq` | `EventSeq.RETURN/ESCAPE/BACKSPACE/DELETE/TAB/...` | event sequences for bindings |
538
+ | `EventSeq` (mouse) | `EventSeq.BUTTON_1/2/3`, `DOUBLE_BUTTON_1/2/3`, `PRIMARY_DOUBLE_CLICK` | mouse event sequences |
539
+ | `EventSeq` (virtual) | `EventSeq.LISTBOX_SELECT/COMBOBOX_SELECTED/NOTEBOOK_TAB_CHANGED/...` | virtual events |
540
+
541
+ ---
542
+
543
+ ## Schema Export
544
+
545
+ `app.schema()` returns a JSON-compatible snapshot of registered widgets
546
+ (`name`, `kind`, `label`, `role`, `description`, plus kind-specific fields).
547
+
548
+ ```python
549
+ @app.label("temperature")
550
+ def t():
551
+ return "25°C"
552
+
553
+ app.schema()
554
+ # → {"title": "...", "widgets": [{"name": "temperature", "kind": "label", ...}]}
555
+ ```
556
+
557
+ ---
558
+
559
+ ## Layout debug
560
+
561
+ After widgets are built, `app.debug_layout()` returns JSON-compatible geometry
562
+ and pack/grid info for every registered widget (useful for clipping, minsize,
563
+ and layout regressions).
564
+
565
+ ```python
566
+ app.run(layout=["msg", "go"]) # or build via tests / custom runner
567
+ print(app.debug_layout())
568
+ # → {"title": "...", "sections": [{"widgets": [{"name": "msg", "geometry": ..., ...}, ...]}]}
569
+ ```
570
+
571
+ ---
572
+
573
+ ## Async-Native (asyncio + Tkinter)
574
+
575
+ `app.run_async()` runs the app on an asyncio event loop, cooperatively scheduled
576
+ with the Tk main loop via `root.tk.dooneevent(0)`.
577
+ `app.spawn(coro)` schedules async tasks during GUI runtime.
578
+ `@app.job(name)` registers async callables.
579
+
580
+ ```python
581
+ @app.job("scan")
582
+ async def scan(vals):
583
+ result = await asyncio.to_thread(some_blocking_call)
584
+ return {"status": "done"}
585
+
586
+ app.run_async(layout=Layout().section("status"))
587
+ ```
588
+
589
+ ## Examples
590
+
591
+ ```bash
592
+ uv run python examples/grid_temp.py # temperature converter
593
+ uv run python examples/task_panel.py # multi-button panel
594
+ uv run python examples/multiscreen.py # order app with screens
595
+ uv run python examples/widget_gallery.py # all widget types
596
+ uv run python examples/header_demo.py # Layout.header / .status chrome
597
+ uv run python examples/combobox_demo.py # ttk.Combobox
598
+ uv run python examples/menubar_demo.py # menubar
599
+ uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async)
600
+ ```
601
+
602
+ ---
603
+
604
+ ## Requirements
605
+
606
+ - Python 3.13+ (`requires-python`; examples default to 3.14 via `Makefile` `PYTHON=...`)
607
+ - Tkinter support in your Python build
608
+ - No other dependencies
609
+
610
+ > Note: On some macOS environments, `uv` + `3.14+freethreaded` can fail at Tk startup with `Can't find a usable init.tcl`.
611
+ > You can switch runtimes per command, e.g. `make run PYTHON=3.13`, `make run PYTHON=3.14+freethreaded`, `make run PYTHON=3.15`.
612
+
613
+ ---
614
+
615
+ ## Related Projects
616
+
617
+ - **`tkinter` (stdlib)**: nextpytk builds on top — adding Decorator / Schema / A11y layers.
618
+ - **`ttk`**: Native look and accessibility; nextpytk prefers ttk widgets where available.
619
+ - **`CustomTkinter`**: Modern look via Canvas rendering. nextpytk takes the opposite approach: use native widgets and embed A11y from the start.
620
+ - **`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.
621
+
622
+ ## License
623
+
624
+ MIT
625
+
626
+ ## Author
627
+
628
+ Takuya Nishimoto — Shuaruta Inc.