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.
- zepygui-0.1.0/CREDITS.md +59 -0
- zepygui-0.1.0/LICENSE +21 -0
- zepygui-0.1.0/PKG-INFO +519 -0
- zepygui-0.1.0/README.md +490 -0
- zepygui-0.1.0/licences/FEATHER-LICENSE.txt +21 -0
- zepygui-0.1.0/licences/LUCIDE-LICENSE.txt +15 -0
- zepygui-0.1.0/pyproject.toml +40 -0
- zepygui-0.1.0/setup.cfg +4 -0
- zepygui-0.1.0/tests/test_forms.py +179 -0
- zepygui-0.1.0/tests/test_log.py +97 -0
- zepygui-0.1.0/tests/test_native.py +90 -0
- zepygui-0.1.0/tests/test_performance.py +182 -0
- zepygui-0.1.0/tests/test_platform.py +218 -0
- zepygui-0.1.0/tests/test_runtime.py +301 -0
- zepygui-0.1.0/tests/test_state.py +107 -0
- zepygui-0.1.0/tests/test_tasks.py +211 -0
- zepygui-0.1.0/tests/test_tree.py +287 -0
- zepygui-0.1.0/zepygui/__init__.py +27 -0
- zepygui-0.1.0/zepygui/__main__.py +61 -0
- zepygui-0.1.0/zepygui/app.py +886 -0
- zepygui-0.1.0/zepygui/desktop.py +149 -0
- zepygui-0.1.0/zepygui/forms.py +324 -0
- zepygui-0.1.0/zepygui/log.py +153 -0
- zepygui-0.1.0/zepygui/macos.py +358 -0
- zepygui-0.1.0/zepygui/reloader.py +67 -0
- zepygui-0.1.0/zepygui/rules.py +10 -0
- zepygui-0.1.0/zepygui/server.py +225 -0
- zepygui-0.1.0/zepygui/state.py +195 -0
- zepygui-0.1.0/zepygui/static/client.js +453 -0
- zepygui-0.1.0/zepygui/static/style.css +508 -0
- zepygui-0.1.0/zepygui/tasks.py +393 -0
- zepygui-0.1.0/zepygui/ui.py +1694 -0
- zepygui-0.1.0/zepygui/window.py +83 -0
- zepygui-0.1.0/zepygui.egg-info/PKG-INFO +519 -0
- zepygui-0.1.0/zepygui.egg-info/SOURCES.txt +35 -0
- zepygui-0.1.0/zepygui.egg-info/dependency_links.txt +1 -0
- zepygui-0.1.0/zepygui.egg-info/top_level.txt +1 -0
zepygui-0.1.0/CREDITS.md
ADDED
|
@@ -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).
|