nextpytk 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ .venv/
2
+ __pycache__/
3
+ dist/
4
+ *.egg-info/
nextpytk-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Takuya Nishimoto
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,301 @@
1
+ Metadata-Version: 2.4
2
+ Name: nextpytk
3
+ Version: 0.2.0
4
+ Summary: Flask-style decorator API for tkinter GUI: schema-driven, A11y-first, agent-compatible.
5
+ Project-URL: Homepage, https://github.com/nishimotz/tk-outer
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).
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ```python
24
+ from nextpytk import TkApp, Layout
25
+
26
+ app = TkApp(title="Hello")
27
+
28
+ @app.status("msg")
29
+ def msg():
30
+ return "Hello, world!"
31
+
32
+ @app.button("greet", label="Greet")
33
+ def on_greet(values):
34
+ return {"msg": "Button clicked!"}
35
+
36
+ app.run(layout=Layout().section("msg").section("greet"))
37
+ ```
38
+
39
+ **Three layout styles — pick the one that fits:**
40
+
41
+ ```python
42
+ # 1) Simple list (easiest)
43
+ app.run(layout=["msg", "greet"])
44
+
45
+ # 2) Fluent DSL
46
+ app.run(layout=Layout().section("msg").section("greet"))
47
+
48
+ # 3) with-block (context manager)
49
+ with app.layout() as b:
50
+ b.section("msg")
51
+ b.section("greet")
52
+ app.run(layout=b.build())
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Multiview (Multi-tab)
58
+
59
+ ```python
60
+ from nextpytk import TkApp, Layout
61
+
62
+ app = TkApp(title="Multi-tab App")
63
+
64
+ @app.status("header")
65
+ def header(): return "Common header"
66
+
67
+ with app.view("Tab1", layout=Layout().section("t1_label", "t1_btn")) as v:
68
+ @v.label("t1_label")
69
+ def t1_label(): return "Tab 1 content"
70
+ @v.button("t1_btn", label="Click")
71
+ def t1_btn(vals): return {}
72
+
73
+ with app.view("Tab2", layout=Layout().section("t2_label")) as v:
74
+ @v.label("t2_label")
75
+ def t2_label(): return "Tab 2 content"
76
+
77
+ @app.multiview(
78
+ "main",
79
+ views=["Tab1", "Tab2"],
80
+ toplevel_widgets=("header",),
81
+ initial_state={"tab": "Tab1"},
82
+ on_tab_change=lambda tab: {"tab": tab},
83
+ )
84
+ def main_multiview(): pass
85
+
86
+ app.run(multiview="main")
87
+ ```
88
+
89
+ View layouts also accept lists or with-block builders:
90
+
91
+ ```python
92
+ @app.multiview("main", views=["Home", "Settings"],
93
+ view_layouts={"Home": ["title", "start"], "Settings": ["timer", "status"]})
94
+ ```
95
+
96
+ ---
97
+
98
+ ## Layout DSL
99
+
100
+ ### Simple list
101
+
102
+ ```python
103
+ app.run(layout=["title", "timer", "start", "status"])
104
+ ```
105
+
106
+ Each name gets its own pack-based section. Extra kwargs forwarded to `section()`:
107
+
108
+ ```python
109
+ Layout.from_list(["a", "b"], fill="both", expand=True)
110
+ ```
111
+
112
+ ### Fluent DSL
113
+
114
+ **Pack sections:**
115
+
116
+ ```python
117
+ Layout().section("msg").section("phase", "count").section("start", "pause")
118
+ ```
119
+
120
+ **Grid builder:**
121
+
122
+ ```python
123
+ from nextpytk.types import Sticky
124
+
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()
132
+ ```
133
+
134
+ Grid builder methods:
135
+
136
+ | Method | Description |
137
+ |--------|-------------|
138
+ | `widget(name, *, sticky, padx, pady, colspan, rowspan)` | Place widget at cursor, advance column |
139
+ | `span(n)` | Set colspan for the next `widget()` call |
140
+ | `next_row()` | Move to next row, reset column |
141
+ | `next_col(n)` | Skip n columns |
142
+ | `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 |
147
+ | `col_minsize(col, px)` | Column minimum width |
148
+ | `row_minsize(row, px)` | Row minimum height |
149
+ | `end_grid()` | Return to Layout chain |
150
+
151
+ `col_weights(0, 1, 1)` means column 0 → weight 0, column 1 → weight 1, column 2 → weight 1.
152
+
153
+ ### With-block (context manager)
154
+
155
+ ```python
156
+ from nextpytk import LayoutBuilder
157
+
158
+ # Standalone builder
159
+ builder = LayoutBuilder()
160
+ with builder:
161
+ builder.section("title")
162
+ with builder.grid(col_weights=(0, 1)):
163
+ builder.widget("celsius", sticky="ew")
164
+ builder.widget("fahrenheit", sticky="ew")
165
+ builder.next_row().span(2).widget("note")
166
+ app.run(layout=builder.build())
167
+
168
+ # Via app.layout() shortcut
169
+ with app.layout() as b:
170
+ b.section("title")
171
+ with b.grid(col_weights=(0, 1)):
172
+ b.widget("celsius", sticky="ew")
173
+ app.run(layout=b.build())
174
+ ```
175
+
176
+ `with b.grid(...)` auto-closes — no `end_grid()` needed.
177
+
178
+ `grid()` options available directly: `col_weights=(0,1)`, `row_weights=(...)`, `padx`, `pady`, `fill`, `expand`, `uniform`.
179
+
180
+ ---
181
+
182
+ ## Widget Reference
183
+
184
+ | Decorator | Widget | Callback receives | Returns |
185
+ |-----------|--------|-------------------|---------|
186
+ | `@app.label(name, font=..., anchor=..., justify=..., padding=...)` | tk.Label | — | `str` or `dict` |
187
+ | `@app.status(name)` | tk.Label (role=status) | — | `str` or `dict` |
188
+ | `@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` |
190
+ | `@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` |
195
+ | `@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` |
198
+ | `@app.canvas(name, width=..., height=...)` | tk.Canvas | — | — |
199
+
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)`
205
+
206
+ `@app.message` creates an auto-wrapping label. `width` sets initial pixel width; `auto_width=True` (default) tracks parent container resize.
207
+
208
+ ---
209
+
210
+ ## Typed Constants
211
+
212
+ ```python
213
+ from nextpytk.types import Side, Fill, Sticky, State, Orient
214
+
215
+ Layout().section("msg", side=Side.LEFT, fill=Fill.X)
216
+ ```
217
+
218
+ Values use `str` literals compatible with tkinter. `SideLike` / `FillLike` etc.
219
+ accept raw strings too.
220
+
221
+ | Type | Namespace | Example |
222
+ |------|-----------|---------|
223
+ | `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
224
+ | `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
225
+ | `Sticky` | `Sticky.NSEW/LEFT_RIGHT/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
226
+ | `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
227
+ | `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale orientation |
228
+ | `Relief` | `Relief.FLAT/RAISED/SUNKEN/GROOVE/RIDGE/SOLID` | border style |
229
+ | `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
230
+ | `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
231
+
232
+ ---
233
+
234
+ ## Schema Export (Agent/LLM)
235
+
236
+ ```python
237
+ @label("temperature")
238
+ def t(): return "25°C"
239
+
240
+ app.schema()
241
+ # → {"title": "...", "widgets": [{"name": "temperature", "kind": "label", ...}]}
242
+ ```
243
+
244
+ Output is JSON-compatible and can serve as LLM Function Calling definitions.
245
+
246
+ ---
247
+
248
+ ## Async-Native (asyncio + Tkinter)
249
+
250
+ `app.run_async()` runs the app on an asyncio event loop, cooperatively scheduled
251
+ with the Tk main loop via `root.tk.dooneevent(0)`.
252
+ `app.spawn(coro)` schedules async tasks during GUI runtime.
253
+ `@app.job(name)` registers async callables.
254
+
255
+ ```python
256
+ @app.job("scan")
257
+ async def scan(vals):
258
+ result = await asyncio.to_thread(some_blocking_call)
259
+ return {"status": "done"}
260
+
261
+ app.run_async(layout=Layout().section("status"))
262
+ ```
263
+
264
+ ## Examples
265
+
266
+ ```bash
267
+ uv run python examples/grid_temp.py # temperature converter
268
+ uv run python examples/task_panel.py # multi-button panel
269
+ uv run python examples/multiscreen.py # order app with screens
270
+ uv run python examples/widget_gallery.py # all widget types
271
+ uv run python examples/disk_usage_flat_viewer.py # ncdu-style viewer (sync)
272
+ uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async)
273
+ ```
274
+
275
+ ---
276
+
277
+ ## Requirements
278
+
279
+ - Python 3.14 by default (`PYTHON=...` override supported)
280
+ - Tkinter support in your Python build
281
+ - No other dependencies
282
+
283
+ > Note: On some macOS environments, `uv` + `3.14+freethreaded` can fail at Tk startup with `Can't find a usable init.tcl`.
284
+ > You can switch runtimes per command, e.g. `make run PYTHON=3.13`, `make run PYTHON=3.14+freethreaded`, `make run PYTHON=3.15`.
285
+
286
+ ---
287
+
288
+ ## Related Projects
289
+
290
+ - **`tkinter` (stdlib)**: nextpytk builds on top — adding Decorator / Schema / A11y layers.
291
+ - **`ttk`**: Native look and accessibility; nextpytk prefers ttk widgets where available.
292
+ - **`CustomTkinter`**: Modern look via Canvas rendering. nextpytk takes the opposite approach: use native widgets and embed A11y from the start.
293
+ - **`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.
294
+
295
+ ## License
296
+
297
+ MIT
298
+
299
+ ## Author
300
+
301
+ Takuya Nishimoto — Shuaruta Inc.
@@ -0,0 +1,290 @@
1
+ # nextpytk — Flask-style Decorator API for Tkinter
2
+
3
+ nextpytk wraps Tkinter in Python decorators, inspired by Flask.
4
+ Widget registration and layout are decoupled via dependency injection.
5
+ All widgets expose a JSON schema for AI/LLM consumption.
6
+ Uses ttk widgets where available (Button, Entry, Checkbutton, Radiobutton, Scale, Spinbox, Notebook).
7
+
8
+ ---
9
+
10
+ ## Quick Start
11
+
12
+ ```python
13
+ from nextpytk import TkApp, Layout
14
+
15
+ app = TkApp(title="Hello")
16
+
17
+ @app.status("msg")
18
+ def msg():
19
+ return "Hello, world!"
20
+
21
+ @app.button("greet", label="Greet")
22
+ def on_greet(values):
23
+ return {"msg": "Button clicked!"}
24
+
25
+ app.run(layout=Layout().section("msg").section("greet"))
26
+ ```
27
+
28
+ **Three layout styles — pick the one that fits:**
29
+
30
+ ```python
31
+ # 1) Simple list (easiest)
32
+ app.run(layout=["msg", "greet"])
33
+
34
+ # 2) Fluent DSL
35
+ app.run(layout=Layout().section("msg").section("greet"))
36
+
37
+ # 3) with-block (context manager)
38
+ with app.layout() as b:
39
+ b.section("msg")
40
+ b.section("greet")
41
+ app.run(layout=b.build())
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Multiview (Multi-tab)
47
+
48
+ ```python
49
+ from nextpytk import TkApp, Layout
50
+
51
+ app = TkApp(title="Multi-tab App")
52
+
53
+ @app.status("header")
54
+ def header(): return "Common header"
55
+
56
+ with app.view("Tab1", layout=Layout().section("t1_label", "t1_btn")) as v:
57
+ @v.label("t1_label")
58
+ def t1_label(): return "Tab 1 content"
59
+ @v.button("t1_btn", label="Click")
60
+ def t1_btn(vals): return {}
61
+
62
+ with app.view("Tab2", layout=Layout().section("t2_label")) as v:
63
+ @v.label("t2_label")
64
+ def t2_label(): return "Tab 2 content"
65
+
66
+ @app.multiview(
67
+ "main",
68
+ views=["Tab1", "Tab2"],
69
+ toplevel_widgets=("header",),
70
+ initial_state={"tab": "Tab1"},
71
+ on_tab_change=lambda tab: {"tab": tab},
72
+ )
73
+ def main_multiview(): pass
74
+
75
+ app.run(multiview="main")
76
+ ```
77
+
78
+ View layouts also accept lists or with-block builders:
79
+
80
+ ```python
81
+ @app.multiview("main", views=["Home", "Settings"],
82
+ view_layouts={"Home": ["title", "start"], "Settings": ["timer", "status"]})
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Layout DSL
88
+
89
+ ### Simple list
90
+
91
+ ```python
92
+ app.run(layout=["title", "timer", "start", "status"])
93
+ ```
94
+
95
+ Each name gets its own pack-based section. Extra kwargs forwarded to `section()`:
96
+
97
+ ```python
98
+ Layout.from_list(["a", "b"], fill="both", expand=True)
99
+ ```
100
+
101
+ ### Fluent DSL
102
+
103
+ **Pack sections:**
104
+
105
+ ```python
106
+ Layout().section("msg").section("phase", "count").section("start", "pause")
107
+ ```
108
+
109
+ **Grid builder:**
110
+
111
+ ```python
112
+ from nextpytk.types import Sticky
113
+
114
+ Layout().grid()
115
+ .span(2).widget("title", sticky=Sticky.W)
116
+ .next_row()
117
+ .widget("label", sticky=Sticky.RIGHT).widget("input", sticky=Sticky.LEFT_RIGHT)
118
+ .next_row()
119
+ .span(2).widget("ok")
120
+ .end_grid()
121
+ ```
122
+
123
+ Grid builder methods:
124
+
125
+ | Method | Description |
126
+ |--------|-------------|
127
+ | `widget(name, *, sticky, padx, pady, colspan, rowspan)` | Place widget at cursor, advance column |
128
+ | `span(n)` | Set colspan for the next `widget()` call |
129
+ | `next_row()` | Move to next row, reset column |
130
+ | `next_col(n)` | Skip n columns |
131
+ | `at(row, col)` | Jump to absolute position |
132
+ | `col_weights(*w)` | Bulk column weights: `col_weights(0, 1, 1)` |
133
+ | `row_weights(*w)` | Bulk row weights |
134
+ | `col_weight(col, w)` | Single column weight |
135
+ | `row_weight(row, w)` | Single row weight |
136
+ | `col_minsize(col, px)` | Column minimum width |
137
+ | `row_minsize(row, px)` | Row minimum height |
138
+ | `end_grid()` | Return to Layout chain |
139
+
140
+ `col_weights(0, 1, 1)` means column 0 → weight 0, column 1 → weight 1, column 2 → weight 1.
141
+
142
+ ### With-block (context manager)
143
+
144
+ ```python
145
+ from nextpytk import LayoutBuilder
146
+
147
+ # Standalone builder
148
+ builder = LayoutBuilder()
149
+ with builder:
150
+ builder.section("title")
151
+ with builder.grid(col_weights=(0, 1)):
152
+ builder.widget("celsius", sticky="ew")
153
+ builder.widget("fahrenheit", sticky="ew")
154
+ builder.next_row().span(2).widget("note")
155
+ app.run(layout=builder.build())
156
+
157
+ # Via app.layout() shortcut
158
+ with app.layout() as b:
159
+ b.section("title")
160
+ with b.grid(col_weights=(0, 1)):
161
+ b.widget("celsius", sticky="ew")
162
+ app.run(layout=b.build())
163
+ ```
164
+
165
+ `with b.grid(...)` auto-closes — no `end_grid()` needed.
166
+
167
+ `grid()` options available directly: `col_weights=(0,1)`, `row_weights=(...)`, `padx`, `pady`, `fill`, `expand`, `uniform`.
168
+
169
+ ---
170
+
171
+ ## Widget Reference
172
+
173
+ | Decorator | Widget | Callback receives | Returns |
174
+ |-----------|--------|-------------------|---------|
175
+ | `@app.label(name, font=..., anchor=..., justify=..., padding=...)` | tk.Label | — | `str` or `dict` |
176
+ | `@app.status(name)` | tk.Label (role=status) | — | `str` or `dict` |
177
+ | `@app.message(name, width=..., auto_width=...)` | tk.Label (auto-wrap) | — | `str` or `dict` |
178
+ | `@app.button(name, label=..., enabled_if=...)` | ttk.Button | entry values `dict` | `dict` |
179
+ | `@app.job(name)` | async callable | entry values `dict` | `dict` |
180
+ | `@app.entry(name, placeholder=..., show=...)` | ttk.Entry | `str` | `dict` |
181
+ | `@app.checkbutton(name, text=...)` | ttk.Checkbutton | `bool` | `dict` |
182
+ | `@app.radiobutton(name, text=..., value=..., group=...)` | ttk.Radiobutton | selected value `str` | `dict` |
183
+ | `@app.text(name, width=..., height=...)` | tk.Text | full content `str` | `dict` |
184
+ | `@app.scale(name, from_=..., to=..., orient=...)` | ttk.Scale | value `str` | `dict` |
185
+ | `@app.spinbox(name, from_=..., to=..., values=...)` | ttk.Spinbox | value `str` | `dict` |
186
+ | `@app.listbox(name, items=..., selectmode=...)` | tk.Listbox | selected item `str` | `dict` |
187
+ | `@app.canvas(name, width=..., height=...)` | tk.Canvas | — | — |
188
+
189
+ Label options:
190
+ - `font`: e.g. `font=("TkDefaultFont", 18, "bold")`
191
+ - `anchor`: e.g. `anchor="e"` (right-aligned)
192
+ - `justify`: multi-line alignment, e.g. `justify="right"`
193
+ - `padding`: e.g. `padding=4` or `padding=(4, 2)`
194
+
195
+ `@app.message` creates an auto-wrapping label. `width` sets initial pixel width; `auto_width=True` (default) tracks parent container resize.
196
+
197
+ ---
198
+
199
+ ## Typed Constants
200
+
201
+ ```python
202
+ from nextpytk.types import Side, Fill, Sticky, State, Orient
203
+
204
+ Layout().section("msg", side=Side.LEFT, fill=Fill.X)
205
+ ```
206
+
207
+ Values use `str` literals compatible with tkinter. `SideLike` / `FillLike` etc.
208
+ accept raw strings too.
209
+
210
+ | Type | Namespace | Example |
211
+ |------|-----------|---------|
212
+ | `Side` | `Side.TOP/BOTTOM/LEFT/RIGHT` | pack side |
213
+ | `Fill` | `Fill.X/Y/BOTH/NONE` | pack fill |
214
+ | `Sticky` | `Sticky.NSEW/LEFT_RIGHT/TOP/BOTTOM/LEFT/RIGHT` | grid sticky |
215
+ | `State` | `State.NORMAL/DISABLED/ACTIVE` | widget state |
216
+ | `Orient` | `Orient.HORIZONTAL/VERTICAL` | scale orientation |
217
+ | `Relief` | `Relief.FLAT/RAISED/SUNKEN/GROOVE/RIDGE/SOLID` | border style |
218
+ | `Justify` | `Justify.LEFT/RIGHT/CENTER` | text alignment |
219
+ | `SelectMode` | `SelectMode.SINGLE/BROWSE/MULTIPLE/EXTENDED` | listbox mode |
220
+
221
+ ---
222
+
223
+ ## Schema Export (Agent/LLM)
224
+
225
+ ```python
226
+ @label("temperature")
227
+ def t(): return "25°C"
228
+
229
+ app.schema()
230
+ # → {"title": "...", "widgets": [{"name": "temperature", "kind": "label", ...}]}
231
+ ```
232
+
233
+ Output is JSON-compatible and can serve as LLM Function Calling definitions.
234
+
235
+ ---
236
+
237
+ ## Async-Native (asyncio + Tkinter)
238
+
239
+ `app.run_async()` runs the app on an asyncio event loop, cooperatively scheduled
240
+ with the Tk main loop via `root.tk.dooneevent(0)`.
241
+ `app.spawn(coro)` schedules async tasks during GUI runtime.
242
+ `@app.job(name)` registers async callables.
243
+
244
+ ```python
245
+ @app.job("scan")
246
+ async def scan(vals):
247
+ result = await asyncio.to_thread(some_blocking_call)
248
+ return {"status": "done"}
249
+
250
+ app.run_async(layout=Layout().section("status"))
251
+ ```
252
+
253
+ ## Examples
254
+
255
+ ```bash
256
+ uv run python examples/grid_temp.py # temperature converter
257
+ uv run python examples/task_panel.py # multi-button panel
258
+ uv run python examples/multiscreen.py # order app with screens
259
+ uv run python examples/widget_gallery.py # all widget types
260
+ uv run python examples/disk_usage_flat_viewer.py # ncdu-style viewer (sync)
261
+ uv run python examples/disk_usage_flat_async.py # ncdu-style viewer (async)
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Requirements
267
+
268
+ - Python 3.14 by default (`PYTHON=...` override supported)
269
+ - Tkinter support in your Python build
270
+ - No other dependencies
271
+
272
+ > Note: On some macOS environments, `uv` + `3.14+freethreaded` can fail at Tk startup with `Can't find a usable init.tcl`.
273
+ > You can switch runtimes per command, e.g. `make run PYTHON=3.13`, `make run PYTHON=3.14+freethreaded`, `make run PYTHON=3.15`.
274
+
275
+ ---
276
+
277
+ ## Related Projects
278
+
279
+ - **`tkinter` (stdlib)**: nextpytk builds on top — adding Decorator / Schema / A11y layers.
280
+ - **`ttk`**: Native look and accessibility; nextpytk prefers ttk widgets where available.
281
+ - **`CustomTkinter`**: Modern look via Canvas rendering. nextpytk takes the opposite approach: use native widgets and embed A11y from the start.
282
+ - **`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.
283
+
284
+ ## License
285
+
286
+ MIT
287
+
288
+ ## Author
289
+
290
+ Takuya Nishimoto — Shuaruta Inc.
@@ -0,0 +1,68 @@
1
+ # ROADMAP — nextpytk
2
+
3
+ nextpytk の開発ロードマップ。
4
+
5
+ ---
6
+
7
+ ## 直近(v0.2.x)
8
+
9
+ ### 型ヒント強化
10
+
11
+ - [ ] デコレータ引数の TypedDict / Protocol 化(Pyright/mypy 補完)
12
+ - [ ] tkinter 定数を活かした Literal 型(`tk.LEFT`, `tk.RIGHT`, `tk.NSEW` など)
13
+ - [ ] `_GridBuilder.columnconfigure` 相当の fluent API
14
+
15
+ ### ウィジェット拡張
16
+
17
+ - [x] ttk widget 対応(ttk.Button, ttk.Entry, ttk.Notebook など)
18
+ - [x] `@app.multiview` デコレータ(マルチタブ)
19
+ - [ ] `bind` イベントの decorator 登録
20
+
21
+ ### A11y 実適用
22
+
23
+ - [ ] Tk 9.1 `::tk::accessible::*` への role/name 結線
24
+ - [ ] `WidgetSpec.role` → 実際の accessibility 属性反映
25
+
26
+ ### Layout DSL 充実
27
+
28
+ - [x] `grid` の `rowconfigure` / `columnconfigure` 相当(`col_weights`/`row_weights`/`rowspan`)
29
+ - [ ] ネストフレーム(`Layout` 内で `Layout` を入れ子に)
30
+ - [ ] `padx`/`pady` のデフォルト値一元設定
31
+
32
+ ### 公開ランタイム API
33
+
34
+ - [x] `build_widgets()`, `widget()`, `widget_kind()`, `widget_specs()`
35
+ - [x] `apply_state()`, `sync()` — カスタムランナーから再利用可能
36
+ - [x] `app.run(multiview="...")` エントリポイント
37
+
38
+ ### Agent / LLM 連携
39
+
40
+ - [ ] `schema()` を Function Calling 定義としての露出改善
41
+ - [ ] `@agent_tool` 統合(GUI 操作をエージェント語彙として扱う)
42
+
43
+ ---
44
+
45
+ ## 中長期
46
+
47
+ ### ttk Style レイヤー
48
+
49
+ - [ ] `Layout.style("my_button", background=..., font=...)` 的なスタイル定義
50
+ - [ ] テーマ切替(`ttk.Style().theme_use(...)`)
51
+
52
+ ### 非同期ジョブ統合
53
+
54
+ - [x] `app.run_async()` + `app.spawn()` — async event loop と Tk の共存
55
+ - [x] `app.spawn(asyncio.to_thread(...))` で非ブロッキングバックグラウンドジョブ
56
+ - [x] 実例同期版: `disk_usage_flat_viewer.py`
57
+ - [x] 実例非同期版: `disk_usage_flat_async.py`(`app.run_async()` + `app.spawn()`)
58
+ - [x] `@app.job(name)` 連携 — async コールバックの @app デコレータ登録
59
+
60
+ ### 宣言的コンポーネント
61
+
62
+ - [ ] `Layout` に代わる `@app.component` デコレータ(React ライクな再利用)
63
+ - [ ] state の型定義とバリデーション(ただし Pydantic は使わない)
64
+
65
+ ### テスト
66
+
67
+ - [ ] ヘッドレス実行テスト(`TKOUTER_HEADLESS=1`)
68
+ - [ ] WidgetSpec 単位のユニットテスト