zepygui 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. zepygui-0.1.0/CREDITS.md +59 -0
  2. zepygui-0.1.0/LICENSE +21 -0
  3. zepygui-0.1.0/PKG-INFO +519 -0
  4. zepygui-0.1.0/README.md +490 -0
  5. zepygui-0.1.0/licences/FEATHER-LICENSE.txt +21 -0
  6. zepygui-0.1.0/licences/LUCIDE-LICENSE.txt +15 -0
  7. zepygui-0.1.0/pyproject.toml +40 -0
  8. zepygui-0.1.0/setup.cfg +4 -0
  9. zepygui-0.1.0/tests/test_forms.py +179 -0
  10. zepygui-0.1.0/tests/test_log.py +97 -0
  11. zepygui-0.1.0/tests/test_native.py +90 -0
  12. zepygui-0.1.0/tests/test_performance.py +182 -0
  13. zepygui-0.1.0/tests/test_platform.py +218 -0
  14. zepygui-0.1.0/tests/test_runtime.py +301 -0
  15. zepygui-0.1.0/tests/test_state.py +107 -0
  16. zepygui-0.1.0/tests/test_tasks.py +211 -0
  17. zepygui-0.1.0/tests/test_tree.py +287 -0
  18. zepygui-0.1.0/zepygui/__init__.py +27 -0
  19. zepygui-0.1.0/zepygui/__main__.py +61 -0
  20. zepygui-0.1.0/zepygui/app.py +886 -0
  21. zepygui-0.1.0/zepygui/desktop.py +149 -0
  22. zepygui-0.1.0/zepygui/forms.py +324 -0
  23. zepygui-0.1.0/zepygui/log.py +153 -0
  24. zepygui-0.1.0/zepygui/macos.py +358 -0
  25. zepygui-0.1.0/zepygui/reloader.py +67 -0
  26. zepygui-0.1.0/zepygui/rules.py +10 -0
  27. zepygui-0.1.0/zepygui/server.py +225 -0
  28. zepygui-0.1.0/zepygui/state.py +195 -0
  29. zepygui-0.1.0/zepygui/static/client.js +453 -0
  30. zepygui-0.1.0/zepygui/static/style.css +508 -0
  31. zepygui-0.1.0/zepygui/tasks.py +393 -0
  32. zepygui-0.1.0/zepygui/ui.py +1694 -0
  33. zepygui-0.1.0/zepygui/window.py +83 -0
  34. zepygui-0.1.0/zepygui.egg-info/PKG-INFO +519 -0
  35. zepygui-0.1.0/zepygui.egg-info/SOURCES.txt +35 -0
  36. zepygui-0.1.0/zepygui.egg-info/dependency_links.txt +1 -0
  37. zepygui-0.1.0/zepygui.egg-info/top_level.txt +1 -0
@@ -0,0 +1,59 @@
1
+ # Third-party credits
2
+
3
+ ZePyGUI has **no runtime dependencies**: it imports only the Python standard
4
+ library, and nothing is vendored into the repository as a library bundle.
5
+ This file records the third-party material that *is* included — adapted icon
6
+ artwork — and the platform components ZePyGUI relies on at run time without
7
+ shipping them.
8
+
9
+ The complete licence texts of included material are kept in
10
+ [`licences/`](licences/).
11
+
12
+ ## Included material
13
+
14
+ | Material | Where | Used for | Licence | Website | Source repository |
15
+ | --- | --- | --- | --- | --- | --- |
16
+ | [Lucide](https://lucide.dev/) icon paths | `ICONS` in [`zepygui/ui.py`](zepygui/ui.py) | Built-in stroke icons (`ui.icon(...)`) | ISC | [lucide.dev](https://lucide.dev/) | [github.com/lucide-icons/lucide](https://github.com/lucide-icons/lucide) |
17
+ | [Feather](https://feathericons.com/) icon paths | `ICONS` in [`zepygui/ui.py`](zepygui/ui.py) | Built-in stroke icons (`ui.icon(...)`) | MIT | [feathericons.com](https://feathericons.com/) | [github.com/feathericons/feather](https://github.com/feathericons/feather) |
18
+
19
+ Many entries of `ICONS` reproduce or adapt SVG path data from Lucide and its
20
+ predecessor Feather (24×24 grid, 2px stroke, round caps). Others were drawn for
21
+ ZePyGUI in the same style: the `settings` gear is computed in `_gear()`, and the
22
+ circles are produced by `_circ()`. Both licences require their copyright notice
23
+ to accompany copies of the material:
24
+
25
+ - [`licences/LUCIDE-LICENSE.txt`](licences/LUCIDE-LICENSE.txt)
26
+ - [`licences/FEATHER-LICENSE.txt`](licences/FEATHER-LICENSE.txt)
27
+
28
+ ## Platform components used at run time (not shipped)
29
+
30
+ ZePyGUI calls these through the operating system. They are neither copied into
31
+ this repository nor redistributed with an app built on ZePyGUI.
32
+
33
+ | Component | Used for | Licence | Provided by |
34
+ | --- | --- | --- | --- |
35
+ | [Python standard library](https://docs.python.org/3/library/) | Everything: HTTP/WebSocket server, threading, `ctypes` bridge | PSF License | The user's Python installation |
36
+ | [WebKit](https://webkit.org/) (`WKWebView`) | Rendering the UI in the native macOS window | BSD 2-Clause / LGPL 2.1 (WebKit components) | macOS |
37
+ | AppKit, Foundation, Objective-C runtime | Native window, menus, event loop | Apple system frameworks | macOS |
38
+ | [Chromium](https://www.chromium.org/)-based browsers (Edge, Chrome, Brave, Chromium, Vivaldi) | App window on Windows/Linux (`mode="window"`) | BSD 3-Clause (Chromium) and vendor terms | Installed by the user (Edge ships with Windows) |
39
+ | `osascript`, PowerShell / Windows Forms, `zenity` / `kdialog` / `notify-send` | Native file dialogs and notifications | Vendor / GPL tools | The operating system or distribution |
40
+
41
+ ## Fonts
42
+
43
+ No font files are bundled. The default font stack names system fonts
44
+ (San Francisco on macOS, Segoe UI on Windows, Roboto/Helvetica elsewhere) and
45
+ `Inter` only if the user already has it installed.
46
+
47
+ ## Acknowledgements
48
+
49
+ The architecture — a native window hosting the system web engine, with the
50
+ application logic outside the webview — follows the approach popularised by
51
+ [Tauri](https://tauri.app/). The visual language of the design system is
52
+ inspired by contemporary interface kits such as
53
+ [shadcn/ui](https://ui.shadcn.com/) and [Radix](https://www.radix-ui.com/).
54
+ No code from these projects is included.
55
+
56
+ ## Notice
57
+
58
+ When third-party code, icons, fonts or other assets are added to ZePyGUI, add
59
+ them to this file and keep their upstream licence text in `licences/`.
zepygui-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rzafiamy
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.
zepygui-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,519 @@
1
+ Metadata-Version: 2.4
2
+ Name: zepygui
3
+ Version: 0.1.0
4
+ Summary: Beautiful native desktop apps in pure Python, with no dependencies.
5
+ Author: rzafiamy
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/rzafiamy/zepygui
8
+ Project-URL: Repository, https://github.com/rzafiamy/zepygui
9
+ Project-URL: Issues, https://github.com/rzafiamy/zepygui/issues
10
+ Keywords: gui,desktop,webview,native,ui,tauri,reactive
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Environment :: MacOS X :: Cocoa
13
+ Classifier: Environment :: Web Environment
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: MacOS
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Topic :: Software Development :: User Interfaces
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ License-File: CREDITS.md
26
+ License-File: licences/FEATHER-LICENSE.txt
27
+ License-File: licences/LUCIDE-LICENSE.txt
28
+ Dynamic: license-file
29
+
30
+ # ZePyGUI
31
+
32
+ **Beautiful native desktop apps in pure Python. Zero dependencies.**
33
+
34
+ ZePyGUI works like Tauri, but you write Python instead of Rust and JavaScript. Your app runs in
35
+ a real native window using the operating system's web engine, comes with a polished design system,
36
+ and updates itself when your data changes.
37
+
38
+ ```python
39
+ from zepygui import App, State, ui
40
+
41
+ app = App("Hello")
42
+ count = State(0)
43
+
44
+ @app.page("/")
45
+ def home():
46
+ return ui.column(
47
+ ui.h1(f"Clicked {count.value} times"),
48
+ ui.button("Click me", lambda: count.set(count.value + 1)),
49
+ )
50
+
51
+ app.run()
52
+ ```
53
+
54
+ ```bash
55
+ python hello.py
56
+ ```
57
+
58
+ No `pip install`, no Node, no Rust toolchain. You only need Python 3.9+.
59
+
60
+ ---
61
+
62
+ ## Why ZePyGUI
63
+
64
+ | | |
65
+ |---|---|
66
+ | **Native window** | On macOS, a real `NSWindow` + `WKWebView` (the same approach Tauri takes), driven via `ctypes`. Native menu bar, copy/paste, full screen, the title bar follows your theme, and the window remembers its size and position. |
67
+ | **No dependencies** | Only the standard library. Nothing to install, nothing to break. |
68
+ | **Beautiful by default** | 50+ components: buttons, inputs, tables, tabs, modals, toasts, charts, sidebar layouts. Dark and light themes, smooth animations. |
69
+ | **Reactive** | Change a `State` and every window that shows it re-renders automatically, even from background threads. |
70
+ | **Simple** | Your UI is just Python functions that return components. |
71
+ | **Desktop APIs** | Native open/save/folder dialogs, system notifications, open files/URLs. |
72
+ | **Built for heavy work** | Background tasks with progress and cancel (threads, processes or asyncio), streamed command output, a log console for 100,000+ lines, virtual tables for 100,000+ rows. |
73
+ | **Validation** | Rules, live field errors and validated submits, including errors sent back by your backend. |
74
+ | **Dev friendly** | `app.run(reload=True)` restarts the app on save; `debug=True` enables the web inspector. |
75
+
76
+ ### How it works
77
+
78
+ ```
79
+ Your Python code ──► builds a tree of components ──► sent to the window over an in-process bridge
80
+ ▲ │
81
+ └──────────── events (click, input, …) ◄────────────────┘
82
+ ```
83
+
84
+ * **macOS**: native `NSWindow` + `WKWebView`. No server and no network port: Python and the UI talk
85
+ through a WebKit script-message bridge.
86
+ * **Windows / Linux**: a frameless app window using the system's Edge/Chromium (Edge ships with
87
+ Windows), talking to Python over a private, token-protected loopback channel. It's the same API, and
88
+ native WebView2/WebKitGTK backends can be added without changing any app code.
89
+
90
+ ---
91
+
92
+ ## Quick start
93
+
94
+ Install from [PyPI](https://pypi.org/project/zepygui/):
95
+
96
+ ```bash
97
+ python3 -m pip install zepygui
98
+ ```
99
+
100
+ Or, from a clone of [the repository](https://github.com/rzafiamy/zepygui), in editable mode:
101
+
102
+ ```bash
103
+ git clone https://github.com/rzafiamy/zepygui.git
104
+ cd zepygui
105
+ python3 -m pip install -e .
106
+ ```
107
+
108
+ Then create and run an app:
109
+
110
+ ```bash
111
+ python3 -m zepygui new myapp # creates myapp/app.py (with a sidebar, pages, theme toggle)
112
+ python3 myapp/app.py
113
+ ```
114
+
115
+ (Without installing, put your app next to the `zepygui/` folder, or run the examples below, which find it on their own.)
116
+
117
+ Or run the examples:
118
+
119
+ ```bash
120
+ python examples/hello.py # counter
121
+ python examples/todo.py # to-do list
122
+ python examples/dashboard.py # full app: sidebar, live charts, table, modal, settings, file dialogs
123
+ python examples/console.py # log console: 70,000 lines, filter, run commands, export
124
+ python examples/files.py # scan a folder in the background, 100k-row table, hash in a process
125
+ python examples/signup.py # form validation with live errors and server-side errors
126
+ ```
127
+
128
+ ---
129
+
130
+ ## The basics
131
+
132
+ ### Pages
133
+
134
+ ```python
135
+ @app.page("/", title="Home")
136
+ def home():
137
+ return ui.h1("Home")
138
+
139
+ @app.page("/user/{id:int}", title="Profile") # parameters: {name}, {name:int}, {name:float}, {name:path}
140
+ def profile(id):
141
+ return ui.h1(f"User #{id}")
142
+ ```
143
+
144
+ Navigate with `ui.link("Profile", to="/user/3")`, `ui.nav_item(...)`, or `ui.navigate("/user/3")`.
145
+
146
+ ### State: the UI updates itself
147
+
148
+ ```python
149
+ count = State(0) # shared by all windows of the app
150
+
151
+ count.value # read (inside a page, this subscribes the window)
152
+ count.set(5) # write → UI re-renders
153
+ count.value += 1 # also works
154
+ count.update(lambda n: n * 2)
155
+
156
+ todos = State([])
157
+ todos.append("Write docs") # list helpers: append, remove, pop, insert, clear, extend
158
+ todos.value[0] = "Edited"
159
+ todos.notify() # after mutating in place yourself
160
+ ```
161
+
162
+ **Local state** for one page or component uses `ui.use_state`:
163
+
164
+ ```python
165
+ @app.page("/search")
166
+ def search():
167
+ query = ui.use_state("")
168
+ return ui.input(query, placeholder="Search…") # a State as `value` means two-way binding
169
+ ```
170
+
171
+ ### Two ways to write layouts
172
+
173
+ Nested calls:
174
+
175
+ ```python
176
+ ui.card(
177
+ ui.h3("Sign in"),
178
+ ui.input(email, label="Email"),
179
+ ui.button("Continue", login, full=True),
180
+ )
181
+ ```
182
+
183
+ …or `with` blocks:
184
+
185
+ ```python
186
+ with ui.card():
187
+ ui.h3("Sign in")
188
+ ui.input(email, label="Email")
189
+ ui.button("Continue", login, full=True)
190
+ ```
191
+
192
+ ### Reusable components
193
+
194
+ ```python
195
+ @ui.component
196
+ def Counter(label):
197
+ n = ui.use_state(0) # each Counter keeps its own count
198
+ return ui.button(f"{label}: {n.value}", lambda: n.set(n.value + 1))
199
+
200
+ ui.row(Counter("Apples"), Counter("Pears"))
201
+ ```
202
+
203
+ In lists, give items a `key=` so their state follows them when the list is reordered.
204
+
205
+ ### Event handlers
206
+
207
+ Handlers can take zero or one argument; ZePyGUI passes the argument only if your function accepts it.
208
+
209
+ ```python
210
+ ui.button("Save", on_click=save) # save()
211
+ ui.input(on_change=lambda text: print(text)) # gets the text
212
+ ui.input(on_enter=lambda text: send(text))
213
+ ui.select(["S", "M", "L"], on_change=set_size) # gets the chosen option
214
+ ui.el("div", "Hover me", on_mouseenter=lambda e: ...) # any DOM event, gets an Event
215
+
216
+ async def load(): # async handlers work too
217
+ await asyncio.sleep(1)
218
+ data.set(await fetch())
219
+ ```
220
+
221
+ A handler runs on its window's thread, so a slow one freezes that window (ZePyGUI prints a
222
+ warning when a handler takes longer than 250 ms). Move slow work into a [task](#heavy-work).
223
+
224
+ ### App shell layout
225
+
226
+ ```python
227
+ @app.layout
228
+ def layout(content):
229
+ return ui.shell(
230
+ content,
231
+ sidebar=ui.sidebar(
232
+ ui.nav_item("Home", "/", icon="home"),
233
+ ui.nav_item("Inbox", "/inbox", icon="mail", badge=3),
234
+ ui.nav_section("Account"),
235
+ ui.nav_item("Settings", "/settings", icon="settings"),
236
+ title="Acme", logo="zap",
237
+ ),
238
+ header=ui.header(ui.spacer(), ui.theme_toggle()),
239
+ )
240
+ ```
241
+
242
+ ---
243
+
244
+ ## Components
245
+
246
+ **Layout:** `column`, `row`, `grid`, `container`, `card`, `spacer`, `divider`, `scroll_area`,
247
+ `shell`, `sidebar`, `nav_item`, `nav_section`, `header`
248
+
249
+ **Text:** `h1`–`h4`, `text`, `link`, `code`, `code_block`, `markdown`, `kbd`
250
+
251
+ **Inputs:** `button`, `icon_button`, `input`, `textarea`, `checkbox`, `switch`, `slider`, `select`,
252
+ `radio_group`, `segmented`, `form`
253
+
254
+ **Display:** `badge`, `avatar`, `icon`, `image`, `progress`, `spinner`, `alert`, `stat`, `table`,
255
+ `tabs`, `accordion`, `modal`, `tooltip`, `empty_state`, `theme_toggle`
256
+
257
+ **Charts:** `line_chart`, `bar_chart`, `donut`, `sparkline`
258
+
259
+ **Large data:** `virtual_list`, `log_view`, and `table(..., virtual=True)`
260
+
261
+ **Actions:** `toast`, `navigate`, `set_theme`, `toggle_theme`, `set_title`, `copy`, `run_js`, `background`
262
+
263
+ **Desktop:** `open_file`, `save_file`, `choose_folder`, `notify`, `open_url`, `open_path`
264
+
265
+ A few examples:
266
+
267
+ ```python
268
+ ui.button("Delete", on_click=delete, variant="danger", icon="trash") # primary/secondary/outline/ghost/soft/danger/success
269
+ ui.input(email, label="Email", type="email", icon="mail", error="Invalid email" if bad else None)
270
+ ui.switch("Dark mode", dark)
271
+ ui.slider(volume, min=0, max=100, label="Volume", format=lambda v: f"{v}%")
272
+ ui.grid(ui.stat("Revenue", "$12k", delta="+8%", icon="dollar"), ..., cols=4)
273
+
274
+ ui.table(users, [
275
+ ("name", "Name"),
276
+ {"key": "role", "label": "Role", "format": lambda v: ui.badge(v, "blue")},
277
+ {"key": "spent", "label": "Spent", "align": "right", "format": lambda v: f"${v:,}"},
278
+ ], on_row_click=open_user) # sortable by default
279
+
280
+ ui.tabs({"Profile": profile_tab, "Billing": billing_tab}, variant="pills")
281
+ ui.modal(show_dialog, ui.text("Are you sure?"), title="Confirm",
282
+ footer=[ui.button("Cancel", close, variant="ghost"), ui.button("Yes", confirm)])
283
+ ui.line_chart({"Sales": [3, 5, 4, 8], "Costs": [2, 3, 3, 4]}, ["Q1", "Q2", "Q3", "Q4"])
284
+ ui.donut({"Free": 120, "Pro": 45, "Team": 12}, center_label="users")
285
+ ```
286
+
287
+ Every component accepts `cls=`, `style=` and `key=`, and any other keyword becomes an HTML attribute.
288
+ Spacing (`gap`, `padding`) uses a 4px scale: `gap=4` is 16px. Strings like `"1.5rem"` pass through.
289
+
290
+ ### Desktop features
291
+
292
+ ```python
293
+ path = ui.open_file("Choose an image", types=["png", "jpg"]) # None if cancelled
294
+ paths = ui.open_file(multiple=True)
295
+ target = ui.save_file("Export", "report.csv")
296
+ folder = ui.choose_folder()
297
+ ui.notify("Export finished", "report.csv was saved") # system notification
298
+ ui.open_path(target, reveal=True) # show in Finder/Explorer
299
+ ```
300
+
301
+ ### Background updates
302
+
303
+ ```python
304
+ @app.timer(1.0)
305
+ def tick():
306
+ clock.set(time.strftime("%H:%M:%S")) # every open window updates
307
+ ```
308
+
309
+ ---
310
+
311
+ ## Heavy work
312
+
313
+ ### Tasks: keep the window responsive
314
+
315
+ `ui.task` runs a function in the background and returns a `Task` at once. Its `status`,
316
+ `progress` and `message` are `State`s, so a page that reads them updates by itself.
317
+
318
+ ```python
319
+ def copy_folder(src, dst, task): # a parameter named `task` receives the Task
320
+ files = list(Path(src).rglob("*"))
321
+ for i, f in enumerate(files):
322
+ task.check() # raises Cancelled once task.cancel() is called
323
+ shutil.copy2(f, dst)
324
+ task.report((i + 1) / len(files), f.name)
325
+ return len(files)
326
+
327
+ job = ui.task(copy_folder, src, dst,
328
+ on_done=lambda n: ui.toast(f"{n} files copied", "success"), # on the window's thread
329
+ on_error=lambda e: ui.toast(str(e), "error"))
330
+
331
+ ui.progress(job.progress.value * 100 if job.progress.value is not None else 0, label=job.message.value)
332
+ ui.button("Cancel", job.cancel)
333
+ ```
334
+
335
+ | Kind of work | How | Runs on |
336
+ |---|---|---|
337
+ | Blocking I/O: files, network, databases | `ui.task(fn, ...)` | shared thread pool (`min(32, cpu + 4)` threads) |
338
+ | `async def` code | `ui.task(coro_fn, ...)`, or an async handler | one shared asyncio loop |
339
+ | CPU-heavy pure functions: hashing, parsing, images | `ui.task(fn, ..., process=True)` | process pool, one process per core |
340
+ | External programs | `ui.run_command(cmd, log=log, on_line=...)` | a reader thread per command |
341
+
342
+ `process=True` re-imports your script in each worker process: guard `app.run()` with
343
+ `if __name__ == "__main__":` (ZePyGUI also refuses to open a window from a worker).
344
+ `ui.background(fn, *args)` is the old spelling of `ui.task(fn, *args)`.
345
+
346
+ Changing `State` from a task is safe and cheap: each window queues at most one redraw and
347
+ renders at most once per frame (60 per second), however fast the values change.
348
+
349
+ ### A log console for 100,000+ lines
350
+
351
+ A `Log` sends each window only the lines it has not seen yet. The window keeps the lines and
352
+ draws only the ones on screen, so appending stays fast however long the log gets.
353
+
354
+ ```python
355
+ log = Log(max_lines=200_000) # oldest lines are dropped beyond this
356
+
357
+ ui.log_view(log, height=480, filter=query, numbers=True) # follows the newest line, tints ERROR/WARN
358
+ ui.run_command(["pytest", "-x"], log=log) # stream a command's output into it
359
+ log.append("done") # from any thread
360
+ print("also works", file=log)
361
+ log.save("session.log"); log.clear()
362
+ ```
363
+
364
+ ### Tables and lists with 100,000+ rows
365
+
366
+ ```python
367
+ ui.table(rows, columns, max_height=480) # above 1,000 rows it renders only the rows in view
368
+ ui.table(rows, columns, virtual=True, row_height=32)
369
+ ui.virtual_list(files, lambda f: ui.row(ui.icon("file"), ui.text(f["name"])), item_height=36, height=480)
370
+ ```
371
+
372
+ Virtual rows have a fixed height and their cells don't wrap.
373
+
374
+ ### Performance numbers
375
+
376
+ `python3 bench/bench.py` measures the runtime headless (no window). Typical results on the development Mac with Python 3.9 (numbers vary by a few percent between runs):
377
+
378
+ | Scenario | Before | Now |
379
+ |---|---|---|
380
+ | Render 5,000 table rows | 3,640 ms | 277 ms |
381
+ | Render a 5,000-row keyed list | 7,107 ms | 511 ms, payload 5.5 → 4.2 MB |
382
+ | Render a 100,000-row table | (minutes) | 5 ms, 12 KB (virtual) |
383
+ | Scroll a 100,000-row table to new rows | — | ~30 ms per step |
384
+ | 50,000 `State.set()` from a thread until the window shows the last | 581 ms, 48 renders | 228 ms, 9 renders |
385
+ | Stream 70,000 log lines into a window | — | 250 ms, 31 messages |
386
+ | Click while slow work runs | 504 ms (blocked) | 22 ms (`ui.task`) |
387
+ | Memory kept after rendering 5,000 rows | 24 MB until the GC runs | 1.9 MB, no garbage cycles |
388
+
389
+ ---
390
+
391
+ ## Validation
392
+
393
+ Give an input a `Field` instead of a `State`: it binds the value, shows the error and marks
394
+ the field required.
395
+
396
+ ```python
397
+ from zepygui import rules, ValidationError
398
+
399
+ @app.page("/signup")
400
+ def signup():
401
+ form = ui.use_form(
402
+ email=("", [rules.required(), rules.email()]),
403
+ password=("", [rules.required(), rules.min_length(8)]),
404
+ confirm=("", [rules.matches("password", "Passwords don't match")]),
405
+ age=(None, [rules.number(min=18, integer=True)]),
406
+ terms=(False, [rules.required("Accept the terms to continue")]),
407
+ )
408
+ return ui.form(
409
+ ui.input(form.email, label="Email"),
410
+ ui.input(form.password, type="password", label="Password"),
411
+ ui.input(form.confirm, type="password", label="Confirm"),
412
+ ui.input(form.age, type="number", label="Age"),
413
+ ui.checkbox("I accept the terms", form.terms),
414
+ ui.button("Create account", submit=True, loading=form.submitting.value),
415
+ on_submit=form.submit(create_account, background=True, reset=True),
416
+ )
417
+
418
+ def create_account(values): # called only when every field is valid
419
+ if db.exists(values["email"]):
420
+ raise ValidationError({"email": "Already registered"}) # shown under the field
421
+ db.insert(values)
422
+ ```
423
+
424
+ * An error appears once the user leaves the field (or submits), then follows what they type.
425
+ * Rules: `required`, `min_length`, `max_length`, `pattern`, `email`, `url`, `number(min, max, integer)`,
426
+ `one_of`, `matches(field)`, and `check(predicate, message)` for your own.
427
+ * A single field: `name = ui.use_field("", rules.required())`, then `ui.input(name)`.
428
+ * `form.values()`, `form.errors()`, `form.valid`, `form.reset()`, `form.set_errors({...})`.
429
+
430
+ ---
431
+
432
+ ## Customizing
433
+
434
+ ```python
435
+ app = App(
436
+ "My App",
437
+ theme="auto", # "auto" (follows the OS), "dark" or "light"
438
+ accent="#10b981", # one color drives the whole palette
439
+ width=1200, height=800, min_size=(600, 400),
440
+ radius=12, # corner roundness
441
+ font="'Inter', sans-serif",
442
+ icon="icon.png", # dock icon (macOS)
443
+ )
444
+
445
+ app.add_css(""".hero { background: linear-gradient(135deg, var(--accent), #ec4899); }""")
446
+ app.static("/assets", "assets") # then ui.image("assets/logo.png")
447
+ ui.register_icon("diamond", "M12 2 22 12 12 22 2 12Z") # SVG paths on a 24x24 grid
448
+ ```
449
+
450
+ Theme variables you can use in your CSS: `--accent`, `--bg`, `--surface`, `--surface-2`, `--border`,
451
+ `--text`, `--text-2`, `--muted`, `--green`, `--red`, `--yellow`, `--blue`, `--radius`, `--shadow`.
452
+
453
+ ### Escape hatches
454
+
455
+ ```python
456
+ ui.el("video", src="intro.mp4", controls=True, autoplay=True) # any HTML tag
457
+ ui.html("<marquee>raw HTML</marquee>") # raw markup (trusted content only)
458
+ ui.run_js("document.body.requestFullscreen()")
459
+
460
+ @app.expose # call Python from your own JavaScript:
461
+ def add(a, b): # const sum = await zepygui.call("add", 2, 3)
462
+ return a + b
463
+ ```
464
+
465
+ ### Run options
466
+
467
+ ```python
468
+ app.run() # native window (macOS) / app window (Windows, Linux)
469
+ app.run(reload=True) # restart on file save, for development
470
+ app.run(debug=True) # right-click → Inspect Element
471
+ app.run(mode="window") # force the Edge/Chromium app window
472
+ app.run(mode="browser") # open in your default browser (handy for dev tools)
473
+ ```
474
+
475
+ ---
476
+
477
+ ## Project layout
478
+
479
+ ```
480
+ zepygui/
481
+ app.py App, routing, per-window sessions, renderer
482
+ ui.py component library
483
+ state.py reactive State
484
+ tasks.py background work: thread pool, process pool, asyncio loop, commands
485
+ log.py streaming log buffer behind ui.log_view
486
+ forms.py validation: Field, Form, rules (also exported as zepygui.rules)
487
+ macos.py native NSWindow + WKWebView via ctypes
488
+ window.py Edge/Chromium app-window launcher (Windows/Linux)
489
+ server.py stdlib HTTP + WebSocket server (fallback transport only)
490
+ desktop.py file dialogs, notifications, open files/URLs
491
+ reloader.py restart on save
492
+ static/ client runtime (DOM patching) + design system CSS
493
+ examples/ hello.py, todo.py, dashboard.py, console.py, files.py, signup.py
494
+ bench/ python3 bench/bench.py (rendering, scheduling, memory, logs, tasks)
495
+ tests/ python3 -m unittest discover -s tests (headless)
496
+ ZEPYGUI_GUI_TESTS=1 python3 -m unittest discover -s tests (+ real native windows, macOS)
497
+ python3 tests/matrix.py (rebuild specs/matrix.md; fails on any uncovered requirement)
498
+ specs/ specification, traceability matrix, manual test suite
499
+ licences/ licence texts of third-party material (see CREDITS.md)
500
+ ```
501
+
502
+ ## Status
503
+
504
+ * **macOS**: native backend, tested.
505
+ * **Windows / Linux**: use the Edge/Chromium app window. If no Chromium browser is installed, the app
506
+ opens in the default browser. Native WebView2 (Windows) and WebKitGTK (Linux) backends are the next step.
507
+ * Packaging into a standalone `.app`/`.exe` is not built in yet; tools such as PyInstaller can bundle
508
+ a ZePyGUI app because it has no dependencies to collect.
509
+
510
+ ## Specifications
511
+
512
+ * [`specs/specification.md`](https://github.com/rzafiamy/zepygui/blob/main/specs/specification.md): what ZePyGUI does, as atomic, testable requirements
513
+ * [`specs/matrix.md`](https://github.com/rzafiamy/zepygui/blob/main/specs/matrix.md): traceability matrix, generated by `python3 tests/matrix.py`
514
+ * [`specs/manual.md`](https://github.com/rzafiamy/zepygui/blob/main/specs/manual.md): manual test procedures for what automation cannot reach
515
+
516
+ ## License
517
+
518
+ MIT License, copyright (c) 2026 rzafiamy. See [`LICENSE`](https://github.com/rzafiamy/zepygui/blob/main/LICENSE).
519
+ Third-party material: [`CREDITS.md`](https://github.com/rzafiamy/zepygui/blob/main/CREDITS.md).