ozdenui 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.
- ozdenui-0.1.0/LICENSE +14 -0
- ozdenui-0.1.0/MANIFEST.in +9 -0
- ozdenui-0.1.0/NOTICE +5 -0
- ozdenui-0.1.0/PKG-INFO +471 -0
- ozdenui-0.1.0/README.md +439 -0
- ozdenui-0.1.0/checks/check_example.py +1310 -0
- ozdenui-0.1.0/checks/check_handbook.py +226 -0
- ozdenui-0.1.0/checks/look.py +504 -0
- ozdenui-0.1.0/checks/run_all.py +69 -0
- ozdenui-0.1.0/checks/run_windows.bat +34 -0
- ozdenui-0.1.0/checks/window_check.py +296 -0
- ozdenui-0.1.0/examples/basics.py +36 -0
- ozdenui-0.1.0/examples/calculator.py +95 -0
- ozdenui-0.1.0/examples/charts.py +75 -0
- ozdenui-0.1.0/examples/chat.py +84 -0
- ozdenui-0.1.0/examples/clock.py +63 -0
- ozdenui-0.1.0/examples/counter.py +25 -0
- ozdenui-0.1.0/examples/csv_viewer.py +77 -0
- ozdenui-0.1.0/examples/dialogs.py +49 -0
- ozdenui-0.1.0/examples/editor.py +124 -0
- ozdenui-0.1.0/examples/file_tool.py +61 -0
- ozdenui-0.1.0/examples/files.py +122 -0
- ozdenui-0.1.0/examples/form.py +90 -0
- ozdenui-0.1.0/examples/gallery-styled.py +401 -0
- ozdenui-0.1.0/examples/gallery.py +337 -0
- ozdenui-0.1.0/examples/game.py +94 -0
- ozdenui-0.1.0/examples/greeting.py +25 -0
- ozdenui-0.1.0/examples/image_viewer.py +88 -0
- ozdenui-0.1.0/examples/job_log.py +66 -0
- ozdenui-0.1.0/examples/json_editor.py +99 -0
- ozdenui-0.1.0/examples/json_viewer.py +63 -0
- ozdenui-0.1.0/examples/markdown_editor.py +98 -0
- ozdenui-0.1.0/examples/minimal_accent.py +109 -0
- ozdenui-0.1.0/examples/minimal_theme.py +234 -0
- ozdenui-0.1.0/examples/paint.py +106 -0
- ozdenui-0.1.0/examples/photos.py +118 -0
- ozdenui-0.1.0/examples/settings.py +79 -0
- ozdenui-0.1.0/examples/todo.py +64 -0
- ozdenui-0.1.0/handbook/01-first-window.md +119 -0
- ozdenui-0.1.0/handbook/02-objects.md +126 -0
- ozdenui-0.1.0/handbook/03-layout.md +127 -0
- ozdenui-0.1.0/handbook/04-events-and-time.md +154 -0
- ozdenui-0.1.0/handbook/05-components.md +229 -0
- ozdenui-0.1.0/handbook/06-dialogs-and-files.md +134 -0
- ozdenui-0.1.0/handbook/07-looks.md +128 -0
- ozdenui-0.1.0/handbook/08-data-and-pictures.md +134 -0
- ozdenui-0.1.0/handbook/09-your-own-component.md +132 -0
- ozdenui-0.1.0/handbook/10-going-further.md +93 -0
- ozdenui-0.1.0/handbook/README.md +53 -0
- ozdenui-0.1.0/handbook/turkce/01-first-window.md +122 -0
- ozdenui-0.1.0/handbook/turkce/02-objects.md +128 -0
- ozdenui-0.1.0/handbook/turkce/03-layout.md +130 -0
- ozdenui-0.1.0/handbook/turkce/04-events-and-time.md +154 -0
- ozdenui-0.1.0/handbook/turkce/05-components.md +231 -0
- ozdenui-0.1.0/handbook/turkce/06-dialogs-and-files.md +139 -0
- ozdenui-0.1.0/handbook/turkce/07-looks.md +129 -0
- ozdenui-0.1.0/handbook/turkce/08-data-and-pictures.md +139 -0
- ozdenui-0.1.0/handbook/turkce/09-your-own-component.md +138 -0
- ozdenui-0.1.0/handbook/turkce/10-going-further.md +96 -0
- ozdenui-0.1.0/handbook/turkce/README.md +56 -0
- ozdenui-0.1.0/make_stubs.py +310 -0
- ozdenui-0.1.0/ozdenui/__init__.py +40 -0
- ozdenui-0.1.0/ozdenui/__init__.pyi +6614 -0
- ozdenui-0.1.0/ozdenui/_kit.py +126 -0
- ozdenui-0.1.0/ozdenui/app.py +851 -0
- ozdenui-0.1.0/ozdenui/components.py +3972 -0
- ozdenui-0.1.0/ozdenui/objects.py +770 -0
- ozdenui-0.1.0/ozdenui/py.typed +0 -0
- ozdenui-0.1.0/ozdenui/values.py +186 -0
- ozdenui-0.1.0/ozdenui/web/basic/LICENSE +14 -0
- ozdenui-0.1.0/ozdenui/web/basic/basic.min.css +1 -0
- ozdenui-0.1.0/ozdenui/web/basic/basic.min.js +78 -0
- ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/LICENSE.txt +206 -0
- ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/OpenSans-Bold.ttf +0 -0
- ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/OpenSans-Regular.ttf +0 -0
- ozdenui-0.1.0/ozdenui/web/basic/scroll-bar.min.js +3 -0
- ozdenui-0.1.0/ozdenui/web/bridge.js +526 -0
- ozdenui-0.1.0/ozdenui/web/comp/accordion.js +659 -0
- ozdenui-0.1.0/ozdenui/web/comp/action-button/add.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/action-button.js +521 -0
- ozdenui-0.1.0/ozdenui/web/comp/action-tab-bar.js +383 -0
- ozdenui-0.1.0/ozdenui/web/comp/app-bar.js +237 -0
- ozdenui-0.1.0/ozdenui/web/comp/badge.js +292 -0
- ozdenui-0.1.0/ozdenui/web/comp/bottom-bar.js +555 -0
- ozdenui-0.1.0/ozdenui/web/comp/breadcrumbs.js +524 -0
- ozdenui-0.1.0/ozdenui/web/comp/canvas.js +652 -0
- ozdenui-0.1.0/ozdenui/web/comp/chat-view.js +827 -0
- ozdenui-0.1.0/ozdenui/web/comp/check-box.js +490 -0
- ozdenui-0.1.0/ozdenui/web/comp/code-editor.js +953 -0
- ozdenui-0.1.0/ozdenui/web/comp/color-picker.js +636 -0
- ozdenui-0.1.0/ozdenui/web/comp/context-menu.js +946 -0
- ozdenui-0.1.0/ozdenui/web/comp/currency-field.js +209 -0
- ozdenui-0.1.0/ozdenui/web/comp/data-table/arrow.svg +2 -0
- ozdenui-0.1.0/ozdenui/web/comp/data-table.js +937 -0
- ozdenui-0.1.0/ozdenui/web/comp/date-picker.js +1055 -0
- ozdenui-0.1.0/ozdenui/web/comp/dialog.js +992 -0
- ozdenui-0.1.0/ozdenui/web/comp/email-field.js +65 -0
- ozdenui-0.1.0/ozdenui/web/comp/file-picker.js +920 -0
- ozdenui-0.1.0/ozdenui/web/comp/filters.js +807 -0
- ozdenui-0.1.0/ozdenui/web/comp/form.js +602 -0
- ozdenui-0.1.0/ozdenui/web/comp/full-screen-select/arrow.svg +2 -0
- ozdenui-0.1.0/ozdenui/web/comp/full-screen-select/cancel.svg +2 -0
- ozdenui-0.1.0/ozdenui/web/comp/full-screen-select.js +614 -0
- ozdenui-0.1.0/ozdenui/web/comp/gauge.js +439 -0
- ozdenui-0.1.0/ozdenui/web/comp/graph-box.js +3911 -0
- ozdenui-0.1.0/ozdenui/web/comp/grid-group.js +185 -0
- ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button/trash-red.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button/trash.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button.js +289 -0
- ozdenui-0.1.0/ozdenui/web/comp/image-view.js +598 -0
- ozdenui-0.1.0/ozdenui/web/comp/item-list.js +1740 -0
- ozdenui-0.1.0/ozdenui/web/comp/json-editor.js +3221 -0
- ozdenui-0.1.0/ozdenui/web/comp/json-view.js +597 -0
- ozdenui-0.1.0/ozdenui/web/comp/label-editor.js +1140 -0
- ozdenui-0.1.0/ozdenui/web/comp/left-menu/data-table.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/left-menu/home.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/left-menu/reports.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/left-menu/setting.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/left-menu.js +548 -0
- ozdenui-0.1.0/ozdenui/web/comp/line-progress-bar.js +343 -0
- ozdenui-0.1.0/ozdenui/web/comp/loading-screen.js +413 -0
- ozdenui-0.1.0/ozdenui/web/comp/log-view.js +530 -0
- ozdenui-0.1.0/ozdenui/web/comp/login-page.js +685 -0
- ozdenui-0.1.0/ozdenui/web/comp/markdown-view.js +1044 -0
- ozdenui-0.1.0/ozdenui/web/comp/menu-bar.js +624 -0
- ozdenui-0.1.0/ozdenui/web/comp/mini-graph-box.js +492 -0
- ozdenui-0.1.0/ozdenui/web/comp/minimal-accent.js +208 -0
- ozdenui-0.1.0/ozdenui/web/comp/notice.js +229 -0
- ozdenui-0.1.0/ozdenui/web/comp/number-field.js +128 -0
- ozdenui-0.1.0/ozdenui/web/comp/numeric-keyboard.js +298 -0
- ozdenui-0.1.0/ozdenui/web/comp/page-control.js +762 -0
- ozdenui-0.1.0/ozdenui/web/comp/page-header.js +191 -0
- ozdenui-0.1.0/ozdenui/web/comp/password-field/hide-btn.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/password-field/hide-btn2.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/password-field/show-btn.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/password-field/show-btn2.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/password-field.js +192 -0
- ozdenui-0.1.0/ozdenui/web/comp/period-bar.js +436 -0
- ozdenui-0.1.0/ozdenui/web/comp/phone-field.js +322 -0
- ozdenui-0.1.0/ozdenui/web/comp/progress-bar.js +992 -0
- ozdenui-0.1.0/ozdenui/web/comp/property-panel.js +3639 -0
- ozdenui-0.1.0/ozdenui/web/comp/radio-button.js +527 -0
- ozdenui-0.1.0/ozdenui/web/comp/range-slider.js +569 -0
- ozdenui-0.1.0/ozdenui/web/comp/rich-text-editor.js +1064 -0
- ozdenui-0.1.0/ozdenui/web/comp/scroll-area.js +308 -0
- ozdenui-0.1.0/ozdenui/web/comp/scrolling-text.js +297 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input/clear-round.svg +1 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input/clear.svg +1 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input/filter.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input/filter.svg +1 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input/search.svg +1 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-input.js +725 -0
- ozdenui-0.1.0/ozdenui/web/comp/search-results.js +596 -0
- ozdenui-0.1.0/ozdenui/web/comp/select-box.js +1862 -0
- ozdenui-0.1.0/ozdenui/web/comp/settings-row.js +299 -0
- ozdenui-0.1.0/ozdenui/web/comp/side-panel.js +616 -0
- ozdenui-0.1.0/ozdenui/web/comp/slider-field.js +952 -0
- ozdenui-0.1.0/ozdenui/web/comp/slider.js +838 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-button/alert-black.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-button/clock.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-button.js +981 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table/clock.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table/down.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table/scroll.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table/sort.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table/up.png +0 -0
- ozdenui-0.1.0/ozdenui/web/comp/smart-table.js +1876 -0
- ozdenui-0.1.0/ozdenui/web/comp/sortable-list.js +1181 -0
- ozdenui-0.1.0/ozdenui/web/comp/spark-line-box.js +738 -0
- ozdenui-0.1.0/ozdenui/web/comp/splitter.js +411 -0
- ozdenui-0.1.0/ozdenui/web/comp/status-bar.js +408 -0
- ozdenui-0.1.0/ozdenui/web/comp/step-bar.js +345 -0
- ozdenui-0.1.0/ozdenui/web/comp/stepper.js +753 -0
- ozdenui-0.1.0/ozdenui/web/comp/tabs.js +1366 -0
- ozdenui-0.1.0/ozdenui/web/comp/tag-input.js +1107 -0
- ozdenui-0.1.0/ozdenui/web/comp/tap-effect.js +170 -0
- ozdenui-0.1.0/ozdenui/web/comp/text-area-field.js +233 -0
- ozdenui-0.1.0/ozdenui/web/comp/text-field.js +1006 -0
- ozdenui-0.1.0/ozdenui/web/comp/time-line.js +398 -0
- ozdenui-0.1.0/ozdenui/web/comp/time-picker.js +1032 -0
- ozdenui-0.1.0/ozdenui/web/comp/tiny-select/arrow.svg +1 -0
- ozdenui-0.1.0/ozdenui/web/comp/tiny-select.js +322 -0
- ozdenui-0.1.0/ozdenui/web/comp/tiny-table.js +283 -0
- ozdenui-0.1.0/ozdenui/web/comp/toast.js +891 -0
- ozdenui-0.1.0/ozdenui/web/comp/toggle.js +757 -0
- ozdenui-0.1.0/ozdenui/web/comp/tool-bar.js +760 -0
- ozdenui-0.1.0/ozdenui/web/comp/tooltip.js +534 -0
- ozdenui-0.1.0/ozdenui/web/comp/tree-view.js +1179 -0
- ozdenui-0.1.0/ozdenui/web/comp/ui-effects.js +1097 -0
- ozdenui-0.1.0/ozdenui/web/comp/ui-standards.js +403 -0
- ozdenui-0.1.0/ozdenui/web/comp/url-field.js +190 -0
- ozdenui-0.1.0/ozdenui/web/comp/web-view.js +588 -0
- ozdenui-0.1.0/ozdenui/web/components.js +122 -0
- ozdenui-0.1.0/ozdenui/web/index.html +96 -0
- ozdenui-0.1.0/ozdenui.egg-info/PKG-INFO +471 -0
- ozdenui-0.1.0/ozdenui.egg-info/SOURCES.txt +207 -0
- ozdenui-0.1.0/ozdenui.egg-info/dependency_links.txt +1 -0
- ozdenui-0.1.0/ozdenui.egg-info/requires.txt +1 -0
- ozdenui-0.1.0/ozdenui.egg-info/top_level.txt +1 -0
- ozdenui-0.1.0/pyproject.toml +50 -0
- ozdenui-0.1.0/setup.cfg +4 -0
- ozdenui-0.1.0/tests/bridge_harness.js +79 -0
- ozdenui-0.1.0/tests/test_bridge.py +146 -0
- ozdenui-0.1.0/tests/test_handbook.py +74 -0
- ozdenui-0.1.0/tests/test_headers.py +133 -0
- ozdenui-0.1.0/tests/test_kit_names.py +378 -0
- ozdenui-0.1.0/tests/test_ozdenui.py +2942 -0
- ozdenui-0.1.0/tests/test_stubs.py +103 -0
- ozdenui-0.1.0/tests/test_types.py +84 -0
ozdenui-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
|
|
2
|
+
Copyright 2020-2026 Bugra Ozden <bugra.ozden@gmail.com> (https://bug7a.github.io/)
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The source package: the tests, the examples and the handbook go with it. (The wheel holds only ozdenui/.)
|
|
2
|
+
# WHY the handbook: the tests run its programs (tests/test_handbook.py); a distribution that tests the source package
|
|
3
|
+
# found none and failed.
|
|
4
|
+
include LICENSE NOTICE README.md make_stubs.py
|
|
5
|
+
recursive-include examples *.py
|
|
6
|
+
recursive-include handbook *.md
|
|
7
|
+
recursive-include tests *.py *.js
|
|
8
|
+
recursive-include checks *.py *.bat
|
|
9
|
+
global-exclude .DS_Store *.pyc
|
ozdenui-0.1.0/NOTICE
ADDED
ozdenui-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ozdenui
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Modern desktop windows in plain Python: the objects and components of basic.js. Only Python, no JavaScript or HTML
|
|
5
|
+
Author: Bugra Ozden
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://bug7a.github.io/ozdenui-python/
|
|
8
|
+
Project-URL: Documentation, https://bug7a.github.io/ozdenui-python/handbook/
|
|
9
|
+
Project-URL: Repository, https://github.com/bug7a/ozdenui-python
|
|
10
|
+
Project-URL: Issues, https://github.com/bug7a/ozdenui-python/issues
|
|
11
|
+
Keywords: gui,desktop,ui,webview,basic.js,beginner
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Education
|
|
15
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
License-File: NOTICE
|
|
30
|
+
Requires-Dist: pywebview<7,>=6
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# OzdenUI
|
|
34
|
+
|
|
35
|
+
A small, modern window of its own in Python, made of [basic.js](https://bug7a.github.io/basic.js/) objects and components.
|
|
36
|
+
Only Python: no JavaScript, no HTML, no browser tab, no server. Your Python code makes the objects, and a window shows them.
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from ozdenui import App, HGroup, Label, SmartButton, VGroup
|
|
40
|
+
|
|
41
|
+
def start(page):
|
|
42
|
+
with VGroup(gap=16):
|
|
43
|
+
count = Label(text="0", font_size=64, bold=True)
|
|
44
|
+
with HGroup(hug=True, gap=12):
|
|
45
|
+
SmartButton(text="−", on_click=lambda: change(count, -1))
|
|
46
|
+
SmartButton(text="+", tone="cyan", on_click=lambda: change(count, +1))
|
|
47
|
+
|
|
48
|
+
def change(count, step):
|
|
49
|
+
count.text = str(int(count.text) + step)
|
|
50
|
+
|
|
51
|
+
App(start, title="Counter", size=(400, 320)).run()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
pip install ozdenui
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
On a Mac with the Python of the system, upgrade pip first (`python3 -m pip install --upgrade pip`): its old pip tries to
|
|
59
|
+
build a part of pywebview from source and fails.
|
|
60
|
+
|
|
61
|
+
**Learn it step by step:** [the handbook](https://github.com/bug7a/ozdenui-python/blob/main/handbook/README.md), ten short chapters for people who know Python and have
|
|
62
|
+
never written a web page (the first window, objects, layout, events, components, dialogs, looks, data, your own
|
|
63
|
+
component, going further). Every program in it is run by the tests. In Turkish:
|
|
64
|
+
[OzdenUI — El Kitabı](https://github.com/bug7a/ozdenui-python/blob/main/handbook/turkce/README.md).
|
|
65
|
+
|
|
66
|
+
**Status: v0.1, experimental.** Tested on macOS (Python 3.9, pywebview 6.2), in the web view of the system (WebKit) and in
|
|
67
|
+
Chromium, the engine of WebView2. Windows itself is checked by the tests of this repository (GitHub Actions); it needs the
|
|
68
|
+
[WebView2 runtime](https://developer.microsoft.com/microsoft-edge/webview2/), which most Windows computers have. Linux needs
|
|
69
|
+
the GTK or Qt web view of [pywebview](https://pywebview.flowrl.com/).
|
|
70
|
+
|
|
71
|
+
## How it works
|
|
72
|
+
|
|
73
|
+
- The window is the web view of the system (WebKit on macOS, WebView2 on Windows), opened by pywebview. The page in
|
|
74
|
+
it holds basic.js and its components; the package carries them (about 3 MB, no internet needed).
|
|
75
|
+
- Your code runs in Python. Every object is a small Python object that sends commands to the page; all the commands
|
|
76
|
+
of a function (start, an event, a timer) go together, in one call.
|
|
77
|
+
- On a Mac: 1000 objects in about 50 ms, a click → your Python function → the screen in about 3 ms, the window shows
|
|
78
|
+
in about 0.5 s.
|
|
79
|
+
|
|
80
|
+
## Objects
|
|
81
|
+
|
|
82
|
+
| Python | basic.js |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `Box`, `VGroup`, `HGroup`, `GridGroup`, `Label`, `Button`, `Input`, `Icon` (`examples/basics.py`) | the same names |
|
|
85
|
+
| `Box(20, 20, 100, 100, color="red")` | `Box(20, 20, 100, 100, { color: "red" })` |
|
|
86
|
+
| `font_size=20` (or `fontSize=20`) | `fontSize: 20` |
|
|
87
|
+
| `with VGroup(gap=10):` ... | `VGroup({ gap: 10 })` ... `endGroup()` |
|
|
88
|
+
| `label.text = "Hi"` or `label.set(text="Hi", bold=True)` | `label.text = "Hi"` |
|
|
89
|
+
| `on_click=fn` or `obj.on("click", fn)` | `obj.on("click", fn)` |
|
|
90
|
+
| `obj.remove()` | `obj.remove()` (the objects inside it too) |
|
|
91
|
+
|
|
92
|
+
- **Groups place their objects.** `VGroup` puts them one under the other, `HGroup` side by side (`gap`, `padding`,
|
|
93
|
+
`align="left top"`, `hug=True` to wrap the content). A group is 100% x 100% by default.
|
|
94
|
+
- **`fill=True`** makes an object take the room the others leave: the height that is left in a `VGroup` (a list between
|
|
95
|
+
a tool bar and a status bar), the width that is left in an `HGroup`. Any object takes it: `LogView(fill=True)`,
|
|
96
|
+
`Splitter(fill=True)`. **`ScrollArea()`** does so and scrolls what is in it, one under the other:
|
|
97
|
+
`with ScrollArea(gap=12, padding=16): ...` (as an object of the page: the whole window; the scroll bar of the kit).
|
|
98
|
+
- **`GridGroup`** puts its objects in columns and rows, in order: `with GridGroup(columns=3, gap=8):`. `columns` is a number (equal
|
|
99
|
+
columns), a list (`[120, "1fr", "auto"]`) or a CSS text. An object fills its cell (`width="100%"` for one with a width of its own), or
|
|
100
|
+
`align="left center"` puts it in its cell. `grid.place(button, column_span=2)` gives it more cells. See `examples/calculator.py`.
|
|
101
|
+
- **Outside a group** an object has a place of its own: `Box(left, top, width, height)`. Without one it starts at the
|
|
102
|
+
top left corner (basic.js itself puts it out of sight until it is placed).
|
|
103
|
+
- **Only a Box or a group holds other objects:** `with Label():` is an error.
|
|
104
|
+
- **Values:** a value you gave is read back without asking the page (`count.text`). A value of the page is asked with
|
|
105
|
+
`obj.measure("width")`. Every text is shown as it is written (a typed `<b>` stays `<b>`): the `text` of a `Label`
|
|
106
|
+
or a `Button` and the texts of the components. HTML of your own: `html="<b>3</b> new"` on a Label or a Button,
|
|
107
|
+
`allow_html=True` on a component. (In basic.js the `text` of a Label is HTML.)
|
|
108
|
+
- **A misspelled name** (`Label(txet="Hi")`) is told in the terminal once, at its line, with the name it may be; so
|
|
109
|
+
is a name of the kit written in camelCase to a class (`TextField(titleText=...)`: write `title_text`). An editor
|
|
110
|
+
(VS Code, PyCharm) offers the params of every class and marks such a name before the program runs (the stubs of
|
|
111
|
+
the package, `ozdenui/__init__.pyi`).
|
|
112
|
+
- **Events:** a handler takes what it needs: `fn()`, `fn(obj)` or `fn(obj, event)`; `event` is a dict
|
|
113
|
+
(`key`, `x`, `y`, `text`...). `obj.on_click = fn` replaces the handler given before; `obj.on("click", fn)` adds
|
|
114
|
+
one more. The text of an `Input` is always current in Python: `name.text`; `Input(on_enter=fn)` runs `fn` on Enter, as a
|
|
115
|
+
`TextField` does. The values of the event go only to the
|
|
116
|
+
parameters without a default, so a value of a loop stays: `lambda number=number: pick(number)`.
|
|
117
|
+
- **The page:** `page.color = "#F4F5F7"`, `page.width` and `page.height` (always current),
|
|
118
|
+
`page.on("resize", fn)`, `page.on("keydown", fn)`.
|
|
119
|
+
- **More of basic.js:** `obj.call("setMotion", "left 0.3s")` calls a method, `obj.ask("getValue")` returns its
|
|
120
|
+
result, `app.evaluate(f"{obj.js}.elem.offsetWidth")` runs JavaScript.
|
|
121
|
+
|
|
122
|
+
## Components
|
|
123
|
+
|
|
124
|
+
A component takes its params by their names in snake_case, and a Python function for a callback. Its state is kept
|
|
125
|
+
in Python (`box.checked`, `select.value`...). Setting it in code does not call the handler, except the `on_complete` of a
|
|
126
|
+
`ProgressBar` (it runs when the value reaches `max_value`), `increase()` and `decrease()` of a `Stepper`, and `add_paths()` of a
|
|
127
|
+
`FilePicker`. The names are the names of the kit in snake_case
|
|
128
|
+
(`TextField`, `min_value`, `max_value`, `description`...). A param is changed later by its name too, where the
|
|
129
|
+
component can change it: `field.unit_text = "kg"`.
|
|
130
|
+
|
|
131
|
+
| Component | Example | State |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `SmartButton` | `SmartButton(text="Save", tone="cyan", on_click=save)` | `button.waiting = True` shows a clock |
|
|
134
|
+
| `TextField` | `TextField(title_text="Name", on_change=fn, on_enter=fn)` | `field.text`, `field.check()` |
|
|
135
|
+
| `EmailField`, `PasswordField` | `EmailField(title_text="E-mail", required=True)` | `field.text`, `field.check()` |
|
|
136
|
+
| `NumberField` | `NumberField(title_text="Count", value=3)` | `field.value` (a number or `None`), `field.text` |
|
|
137
|
+
| `UrlField` | `UrlField(title_text="Site", text="github.com")` | `field.text`: the whole address, with the protocol chosen at its start (`https://github.com`) |
|
|
138
|
+
| `PhoneField` | `PhoneField(title_text="Phone", country_code="+1", unit_text="US")` | `field.text`: the number in its mask (`+1 (555) 123-4567`), `field.is_valid()` |
|
|
139
|
+
| `CurrencyField` | `CurrencyField(title_text="Price", value=1234.5, unit_text="$")` | `field.value` (a number or `None`), `field.text` (`1,234.5`) |
|
|
140
|
+
| `TextAreaField` | `TextAreaField(title_text="Note", height=120, on_change=fn)` | `area.text` |
|
|
141
|
+
| `SearchInput` | `SearchInput(placeholder="Find", on_change=fn, on_enter=fn)` | `box.text` |
|
|
142
|
+
| `CheckBox` | `CheckBox(text="Remember me", checked=True, on_change=fn)` | `box.checked` |
|
|
143
|
+
| `Toggle` | `Toggle(value=True, on_change=fn)` | `toggle.value` |
|
|
144
|
+
| `RadioButton` | `RadioButton(group="size", value="M", text="Medium")` | `RadioButton.value_of("size")` |
|
|
145
|
+
| `SelectBox` | `SelectBox(options=["Apple", "Pear"], value="Pear", on_change=fn)` | `select.value` |
|
|
146
|
+
| `Slider` | `Slider(value=30, min_value=0, max_value=100, width=280, on_change_end=fn)` | `slider.value` |
|
|
147
|
+
| `SliderField` | `SliderField(text="Volume", value=5, min_value=0, max_value=10, on_change_end=fn)` | `slider.value` |
|
|
148
|
+
| `Stepper` | `Stepper(value=2, min_value=0, max_value=10, on_change=fn)` | `stepper.value` |
|
|
149
|
+
| `DatePicker` | `DatePicker(value="2026-11-20", on_change=fn)` | `picker.value`, `picker.date` (a `datetime.date`) |
|
|
150
|
+
| `TimePicker` | `TimePicker(value="19:30", on_change=fn)` | `picker.value`, `picker.time` (a `datetime.time`) |
|
|
151
|
+
| `ColorPicker` | `ColorPicker(value="#3871E0", on_change=fn)` | `picker.value` |
|
|
152
|
+
| `FilePicker` | `FilePicker(accept=".csv,.txt", multiple=True, on_change=fn)` | `picker.files`, `picker.paths` |
|
|
153
|
+
| `ProgressBar` | `bar = ProgressBar(title_text="Copying", max_value=100); bar.value = 40` | `bar.value` |
|
|
154
|
+
| `SortableList` | `SortableList(items=["Sales", "Stock", {"key": "home", "text": "Home", "locked": True}], on_change=fn)` | `lst.items` (your items in the new order), `lst.keys`; `move_item()`, `add_item()`, `remove_item()` |
|
|
155
|
+
| `ItemList` | `ItemList(items=["Apple", {"key": "p", "text": "Pear", "description": "Green"}], on_change=fn)` | `lst.items`, `lst.selected_keys`, `lst.selected` (a text item becomes `{"key": "0", "text": "Apple"}`; `"enabled": False` can not be chosen). Pictures: `layout="grid", style_name="gallery"` (a dark window takes `"galleryDark"`), an item `{"text": "Sea", "icon_file": "sea.jpg"}` (a path, bytes, a Pillow image or an address) |
|
|
156
|
+
| `SmartTable` | `SmartTable(columns=[{"key": "name", "title": "Name"}], rows=[...], height=300, on_select=fn)` | `table.rows`, `table.selected_row` |
|
|
157
|
+
| `TreeView` | `TreeView(items=[{"key": "a", "text": "A", "children": ["b"]}], height=300, on_select=fn)` | `tree.value`, `tree.selected_node` |
|
|
158
|
+
| `Tabs` | `tabs = Tabs(tabs=["A", "B"])`, then `with tabs.panel("A"): ...` | `tabs.value` |
|
|
159
|
+
| `Accordion` | `acc = Accordion(sections=["A", "B"])`, then `with acc.section("A"): ...` | `acc.open_keys` |
|
|
160
|
+
| `GraphBox` | `GraphBox(type="bar", labels=["Jan", "Feb"], values=[12, 19], format_value="{:,.0f} TL")` | `graph.set_values([...])`, `graph.save_image("chart.png")` |
|
|
161
|
+
| `LogView` | `log = LogView(height=300); log.info("Reading"); log.error("Failed")` | `log.get_text()` |
|
|
162
|
+
| `ImageView` | `view = ImageView(height=400); view.set_image(figure)` | `view.zoom_level`, `view.natural_width` |
|
|
163
|
+
| `Canvas` | `canvas = Canvas(height=400); canvas.circle(100, 80, 30, fill="orange", draggable=True)` | `canvas.shapes`, `canvas.shape(key)`; `line()`, `rect()`, `polygon()`, `text()`, `image()`, `update_shape()`, `move_shape()`, `save_image()`; `drawing=True`: a pen (`on_draw_end`) |
|
|
164
|
+
| `StepBar` | `StepBar(steps=["Cart", "Address", "Payment"], step=1, on_step_click=fn)` | `bar.step`, `next()`, `previous()` |
|
|
165
|
+
| `RangeSlider` | `RangeSlider(low=200, high=800, max_value=1000, step=50, width=280, on_change_end=fn)` | `slider.low`, `slider.high`, `set_values(100, 500)` |
|
|
166
|
+
| `MarkdownView` | `view = MarkdownView(text="# A report\n\nSome **bold** words"); view.append_text("More")` | `view.text`, `view.headings` |
|
|
167
|
+
| `ChatView` | `ChatView(empty_text="Ask something", on_send=answer)`; in `answer`: `key = chat.add_message(role="assistant")`, then `chat.append_text(key, piece)` | `chat.messages`, `chat.busy` (the dots, Stop) |
|
|
168
|
+
| `JsonView` | `JsonView(value=answer, height=400, on_select=fn)` | `view.selected_path`, `view.selected_value` |
|
|
169
|
+
| `JsonEditor` | `JsonEditor(value=products, height=360, on_change=fn)` | `editor.value` (asks the page), `editor.set_value([...])`; `JsonEditor.open(value=settings, on_apply=fn)` is a window |
|
|
170
|
+
| `MenuBar` | `MenuBar(menus=[{"text": "File", "items": [{"text": "Save", "shortcut": "Ctrl+S", "on_click": save}]}])` | `set_item_checked(key, True)` |
|
|
171
|
+
| `ToolBar` | `ToolBar(items=[{"key": "save", "icon": "save", "hint": "Save", "on_click": save}, "-", {"key": "bold", "icon": "bold", "toggle": True}])` | `bar.checked_keys`, `set_item_enabled(key, False)` |
|
|
172
|
+
| `ContextMenu` | `ContextMenu(target=tree, items=["Open", "-", {"text": "Delete", "on_click": fn}])` | `menu.open_at(x, y)` |
|
|
173
|
+
| `StatusBar` | `bar = StatusBar(message="Ready", items=["UTF-8"]); bar.set_message("Saved", "success", 3000)` | `set_busy(True)` shows a circle |
|
|
174
|
+
| `Splitter` | `split = Splitter(first_size=240)`, then `with split.first: ...` and `with split.second: ...` | `split.first_size_now` |
|
|
175
|
+
| `CodeEditor` | `CodeEditor(value="x = 1", language="python", height=300, on_change=fn)` | `editor.value`, `editor.cursor_line` |
|
|
176
|
+
| `Tooltip` | `Tooltip(target=button, text="Saves the file")` | `Tooltip.show(button, "Saved.", time=2000)` |
|
|
177
|
+
| `SidePanel` | `panel = SidePanel(title_text="Filters")`, then `with panel.body: ...` and `panel.open()` | `panel.is_open` (also after its cover or Escape closed it) |
|
|
178
|
+
| `LoadingScreen` | `loading = LoadingScreen(title_text="My App", message_text="Reading...")`, later `loading.close()` | `set_message()`, `set_progress(0.4)`; it covers the window (`cover=box`: a box) |
|
|
179
|
+
| `TagInput` | `TagInput(tags=["audio"], suggestions=["audio", "fitness"], on_change=fn)` | `field.tags` |
|
|
180
|
+
| `DataTable` | `DataTable(columns=[{"key": "name", "title": "Name"}], rows=[...], on_select=fn)` | `table.selected_row` (the row as it was given) |
|
|
181
|
+
| `WebView` | `WebView(html=figure.to_html(), height=400)`, `WebView(url="https://example.com")` | `view.load(address)`, `load_html()`, `load_file()`, `post_message(data)`; `on_message(view, data)` |
|
|
182
|
+
|
|
183
|
+
- **`TextField`** and the fields made from it: `required=True` and the rules of a field show a warning on the field. `if name.check() and
|
|
184
|
+
email.check(): save()` shows every warning and tells if there is none. `on_change` runs at every change, `on_enter` on Enter.
|
|
185
|
+
`max_length` and `min_length` (a `PasswordField`) count the letters. A `PhoneField` writes the country code and the marks of
|
|
186
|
+
its mask (`phone_mask=" (___) ___-____"`) by itself, and a number given in code or pasted goes into it (`"0555 123 45 67"`);
|
|
187
|
+
the defaults of the kit are `"+90"` and `"TR"`. A `CurrencyField` writes its number in groups (`group_separator`,
|
|
188
|
+
`decimal_separator`, `max_decimal_digits`). A `UrlField` starts with `https://` (the kit: `https://www.`); a change of its
|
|
189
|
+
protocol calls `on_change` too.
|
|
190
|
+
- **`SortableList`**: the user changes the order: the grip at the left drags a row (`drag_by="row"`: the whole row), and with the
|
|
191
|
+
keys Space grabs a row, the arrows move it and Space drops it. `lst.items` gives back your own items (the same texts and dicts)
|
|
192
|
+
in the new order. `on_change(lst, item, from_index, to_index)`; `removable=True` adds an x button (`on_remove`),
|
|
193
|
+
`show_buttons=True` up and down buttons; an item with `"locked": True` does not move. A change in code is silent.
|
|
194
|
+
- **`FilePicker`** gives real paths: a file dropped on it or chosen after a click (the file dialog of the system) reaches Python as
|
|
195
|
+
`{"name", "path", "size", "size_text", "type", "extension", "last_modified"}`, so `open(file["path"])` reads it. `picker.add_paths()`
|
|
196
|
+
adds files from code. A folder dropped on it is not read; `directory=True` chooses a folder with a click. `icon_file`: a
|
|
197
|
+
picture of yours in the place of its icon.
|
|
198
|
+
- **`GraphBox`**: line, area, bar, pie, doughnut, radar, scatter, bubble; no other library is needed. The values may be numpy arrays or a
|
|
199
|
+
pandas Series. `format_value` is a text and not a function, because the page needs the text of a number while it draws:
|
|
200
|
+
`{}`, `{:.2f}`, `{:,}`, `{:,.0f} TL`, `{:d}`, `{:.1%}`.
|
|
201
|
+
- **`TreeView`** reads a folder only when it opens: a node with `"has_children": True` and no children calls
|
|
202
|
+
`on_load_children(tree, node)`, which answers with `tree.set_children(node["key"], [...])`. See `examples/files.py`.
|
|
203
|
+
A node: `key`, `text`, `description` (a soft text at its end, Ex: a size), `tag`, `icon_file`, `enabled`, `children`.
|
|
204
|
+
- **`Accordion`**: sections that fold. `multiple=False` keeps one open. A closed section keeps its objects, and objects can be made in it
|
|
205
|
+
while it is closed. A section: a text, or `{"key", "text", "description", "opened", "enabled"}`.
|
|
206
|
+
- **The items of a list** (an `ItemList`, a `TreeView`, a `StepBar`, an `Accordion`, `Tabs`, a `StatusBar`, a `ToolBar`, a `SortableList`)
|
|
207
|
+
take the keys of the kit. An item with no `text` and a key its list does not know (`"title"`) would be an empty row:
|
|
208
|
+
the terminal tells it once, at the line of the program:
|
|
209
|
+
`ozdenui: shop.py:12: ItemList: an item has no "text"; "title" is not a key of its items (key, text, description, ...)`.
|
|
210
|
+
- **`Tooltip`** has three forms: a hint under the mouse, `Tooltip.show()` a bubble at an object, and
|
|
211
|
+
`Tooltip.popover(button, make_content)`, a bubble that stays and holds objects: `make_content()` makes them when it opens.
|
|
212
|
+
- **`SmartTable`** makes only the rows that are seen: 100000 rows reach it in about 2 s on a Mac, and then it stays fast.
|
|
213
|
+
A row is a dict by the keys of the columns, or a list in their order.
|
|
214
|
+
- **`LogView`** takes the output of your program: `log.capture_print()` sends `print()` into it (and the errors, as
|
|
215
|
+
errors), `logging.getLogger().addHandler(log.logging_handler())` the `logging` module.
|
|
216
|
+
- **`ImageView`** and `Icon(picture=...)` take a path, bytes, a Pillow image, a matplotlib figure or an address
|
|
217
|
+
(`picture_address()` makes the address). A new picture of the same size keeps the zoom and does not blink, so a
|
|
218
|
+
program can keep drawing its plot. A picture goes to the page as a `data:` address: 8 MB in about 150 ms, 32 MB in
|
|
219
|
+
about 0.7 s on a Mac. (The web view of macOS refuses a `file://` picture outside the folder of the page.)
|
|
220
|
+
- **`MarkdownView`**: a report, or the answer of an AI as it comes (`append_text()`: what is above stays, and the view follows the end
|
|
221
|
+
until the reader scrolls up). A text is never read as HTML and no script of it runs. `` reads a file of this
|
|
222
|
+
computer (`picture_folder=`, or next to the file of `load_file()`); a matplotlib figure: `f"})"`.
|
|
223
|
+
A picture with an `http` address stays a link, because its address can carry data (the answer of an AI); a text you trust: `remote_pictures=True`. `on_link_click(view, address)`
|
|
224
|
+
takes the links; without it the browser of the computer opens them.
|
|
225
|
+
- **`ChatView`**: a conversation with an AI. The user writes at the bottom (Enter sends, Shift+Enter makes a new line) and
|
|
226
|
+
`on_send(chat, text)` gets the text when its bubble is on the view already. An answer is Markdown: `key =
|
|
227
|
+
chat.add_message(role="assistant")`, `chat.busy = True` (three dots until the first words, Stop in the place of Send),
|
|
228
|
+
then `chat.append_text(key, piece)` for every piece, from a background job too (`run_in_background`), and `chat.busy =
|
|
229
|
+
False`. Stop calls `on_stop(chat)`: stop your answer there. `chat.messages` keeps the conversation (`{"key", "role",
|
|
230
|
+
"text"}`), `set_messages()` starts another one. `examples/chat.py` writes its answers itself, word by word: your AI
|
|
231
|
+
goes in its place.
|
|
232
|
+
- **`JsonView`** shows any value, also a date, a set or an object of yours (as its text). A branch is made when it opens and a long list
|
|
233
|
+
comes in pages, so a list of a million items opens at once. A click tells the path of a row (`users[0].name`); right click or Ctrl+C copies.
|
|
234
|
+
- **`JsonEditor`** writes a list or a dict of JSON in a table: a click on a cell writes in it, a title sorts, rows come pasted
|
|
235
|
+
from a spreadsheet or as a JSON list, and a cell that holds a list or a dict opens as a table of its own. `on_change(editor,
|
|
236
|
+
value)` gets the whole value at every change; `editor.value` asks the page (`None`, and `editor.error_text`, while the table
|
|
237
|
+
can not make one: a name used twice). Only JSON data goes in: a date is refused, as `json` does (it would come back as a text).
|
|
238
|
+
`columns=[{"key": "unit", "options": ["kg", "box"]}]` gives the choices of a cell; `texts={"add_row": "Satır"}` its own texts.
|
|
239
|
+
`JsonEditor.open(...)` is the editor in a window with Cancel and Apply: `on_apply(value)`.
|
|
240
|
+
- **`WebView`** shows a web page or an HTML text, with its scripts: a plotly chart (`fig.to_html()`), a folium map
|
|
241
|
+
(`m.get_root().render()`), a table of pandas (`df.to_html()`). A script, a map tile or a picture from the internet needs a
|
|
242
|
+
connection; `fig.to_html()` carries plotly in it (3.5 MB arrive in about 0.25 s). A path of this computer (`url="report.html"`,
|
|
243
|
+
`load_file()`) shows the text of the file: a picture or a style next to it is not loaded, because the web view opens no file
|
|
244
|
+
outside the folder of the package. `on_message(view, data)` gets `window.parent.postMessage(data, "*")` of the page;
|
|
245
|
+
`post_message(data)` sends to it. `auto_height=True` grows the box with its page. A site may refuse to be shown in a frame.
|
|
246
|
+
- **`MenuBar`** is in the page, in the look of the window: it is not the menu bar of the system.
|
|
247
|
+
- **`ToolBar`**: the row of buttons under the menus. An icon is a name of `ToolBar.ICONS` or your own SVG text; `"-"` is a line, `" "`
|
|
248
|
+
sends the next ones to the right end, `toggle=True` and `group="align"` stay pressed, `items` opens a menu. What does not fit goes
|
|
249
|
+
into a "..." menu and comes back when there is room. A key that is also in a `MenuBar` runs once.
|
|
250
|
+
- **`CodeEditor`**: Python, JavaScript, JSON, HTML, CSS, SQL and Bash; only the lines that are seen are drawn. Typing is
|
|
251
|
+
the text box of the web view, so undo, selection and the input of every language work.
|
|
252
|
+
- **`framed=False`** takes the frame of a component: no border and no rounded corners on its box, for one that fills the
|
|
253
|
+
room between two bars (a `LogView`, a `CodeEditor`, an `ImageView`, a `Canvas`, a `JsonEditor`). It is
|
|
254
|
+
`style={"box": {"border": 0, "round": 0}}` in one word; a key of your own `style` wins. See `examples/editor.py`.
|
|
255
|
+
|
|
256
|
+
The other components of basic.js (about 40 in the package) work without a Python class of their own:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
from ozdenui import App, Component
|
|
260
|
+
|
|
261
|
+
def start(page):
|
|
262
|
+
Component("Gauge", 20, 20, 180, 180, value=72, sub_text="Disk")
|
|
263
|
+
|
|
264
|
+
App(start, components=["Gauge"]).run() # loads the component and what it needs
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Your own component
|
|
268
|
+
|
|
269
|
+
A component of your own is a Python class that makes its objects in a `with` block. No JavaScript is needed:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from ozdenui import App, HGroup, Label, SmartButton, VGroup
|
|
273
|
+
|
|
274
|
+
CARD_LOOK = dict(width=220, height="auto", align="left top",
|
|
275
|
+
padding=16, gap=8, color="white", round=12)
|
|
276
|
+
|
|
277
|
+
class Card:
|
|
278
|
+
"""A card: a title, a text and a button."""
|
|
279
|
+
|
|
280
|
+
def __init__(self, title, text, on_open=None):
|
|
281
|
+
self.on_open = on_open
|
|
282
|
+
with VGroup(**CARD_LOOK) as self.box:
|
|
283
|
+
self.title = Label(text=title, font_size=18, bold=True)
|
|
284
|
+
Label(text=text, text_color="#5A6270")
|
|
285
|
+
SmartButton(text="Open", on_click=self.open)
|
|
286
|
+
|
|
287
|
+
def open(self):
|
|
288
|
+
if self.on_open:
|
|
289
|
+
self.on_open(self)
|
|
290
|
+
|
|
291
|
+
def set_title(self, title):
|
|
292
|
+
self.title.text = title
|
|
293
|
+
|
|
294
|
+
def start(page):
|
|
295
|
+
page.color = "#F4F5F7"
|
|
296
|
+
with HGroup(gap=16, padding=24, wrap=True):
|
|
297
|
+
for name in ["Sales", "Stock", "Orders"]:
|
|
298
|
+
Card(name, "Updated today.",
|
|
299
|
+
on_open=lambda card: card.set_title("Opened"))
|
|
300
|
+
|
|
301
|
+
App(start, title="Cards", size=(760, 240)).run()
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
- **A plain class, not a subclass of `Box`:** the class keeps its objects (`self.box`, `self.title`). A name set on
|
|
305
|
+
a basic.js object goes to the page as one of its values (`box.title = ...` changes the box, it keeps no label).
|
|
306
|
+
- **Methods change it** (`set_title()`), as the `setX()` methods of the components of basic.js do.
|
|
307
|
+
- **Share it as a file:** put it in `card.py`, then `from card import Card` in every program that needs it.
|
|
308
|
+
- Its objects are ordinary objects: `card.box.remove()` removes the card with the objects inside it,
|
|
309
|
+
`card.box.on("click", fn)` and `card.title.measure("width")` work as on any other object.
|
|
310
|
+
- **The rule of thumb:** "a click, then a change" belongs to Python; "every frame, every mouse move" belongs to the page.
|
|
311
|
+
- **A callback that must answer at once** can not be a Python function, Ex: the function that draws a row of a list
|
|
312
|
+
(`createItemView`). See "Limits of v0.1".
|
|
313
|
+
- **A fast, continuous event** (dragging, following the mouse) sends every move to Python and stutters: give the motion
|
|
314
|
+
to the page instead (`obj.call("setMotion", "left 0.3s")`, then `obj.left = 200`: the page moves it smoothly).
|
|
315
|
+
A `Canvas` does so by itself: the page draws the free lines and the drags, Python hears only their ends.
|
|
316
|
+
|
|
317
|
+
## Light and dark
|
|
318
|
+
|
|
319
|
+
One word makes the whole window dark: every component takes its dark look, the dialogs and the toasts too, and the page,
|
|
320
|
+
a `Label`, a `Box` and an `Input` follow. A `tone` of a `SmartButton` becomes its dark tone.
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
App(start, theme="dark").run() # "light" (default), "dark", or "auto": what the system asks
|
|
324
|
+
page.app.theme # "light" or "dark": for the colors of your own objects
|
|
325
|
+
page.app.set_theme("dark") # the window again in another theme: its page is made again, start() runs again
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
A look you give stays: `CheckBox(style_name="modern")`, `Label(text_color="red")`. A component without a dark look keeps
|
|
329
|
+
its light one (`Component("Filters")`).
|
|
330
|
+
|
|
331
|
+
`LogView`, `ImageView`, `MarkdownView`, `JsonView`, `ChatView`, `RangeSlider`, `StepBar`, `Canvas`, `MenuBar`, `ToolBar`, `StatusBar`, `Splitter`, `CodeEditor`, `Accordion` and `GraphBox` also change their theme while they are
|
|
332
|
+
on the page; what they show stays:
|
|
333
|
+
|
|
334
|
+
```python
|
|
335
|
+
log.set_theme("dark") # log.theme = "dark" does the same
|
|
336
|
+
page.color = "#1C1C1B" # the page is yours
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
The other components take their look when they are made.
|
|
340
|
+
|
|
341
|
+
## Minimal
|
|
342
|
+
|
|
343
|
+
`App(style="minimal")` gives the components their minimal look: small (28 px controls, 13 px text) and made of lines,
|
|
344
|
+
with no fill and no shadow of their own, so they take the ground of the window; with `theme="dark"` the minimalDark
|
|
345
|
+
look. About 40 components have it (the fields, the choices, the pickers, the lists, the tables, the bars, the dialogs, a
|
|
346
|
+
`GraphBox`...); the others keep their usual look.
|
|
347
|
+
|
|
348
|
+
```python
|
|
349
|
+
App(start, style="minimal").run() # also theme="dark"
|
|
350
|
+
SmartButton(text="Send", tone="ink") # the main action: the one filled button
|
|
351
|
+
SmartButton(text="Delete", style_name="minimalDanger") # a delete (minimalDangerDark in a dark window)
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
`examples/minimal_theme.py` shows every one of them (`python minimal_theme.py dark`: in a dark window).
|
|
355
|
+
|
|
356
|
+
**In a color:** `accent` gives the minimal look one color of yours. It goes where the look draws its ink: an on toggle,
|
|
357
|
+
a checked box, the fill of a slider and of a progress bar, the selected tab, row and step, the edge of a field while
|
|
358
|
+
typing, and the main button (`tone="accent"`). The lines, the texts and the focus ring stay as they are.
|
|
359
|
+
|
|
360
|
+
```python
|
|
361
|
+
App(start, style="minimal", accent="#2A78D6").run() # choose a color that reads on white
|
|
362
|
+
App(start, style="minimal", accent="#2A78D6", accent_dark="#7FB6DE", theme="dark").run()
|
|
363
|
+
SmartButton(text="Send", tone="accent") # the main action of any window ("ink" without an accent)
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
In a dark window the color is made lighter until it reads on the dark ground, unless `accent_dark` gives it. The text
|
|
367
|
+
on the color is white or dark, the one that reads better. Any other color of a component can still be given with its
|
|
368
|
+
`style`. `examples/minimal_accent.py` shows it (`python minimal_accent.py dark`).
|
|
369
|
+
|
|
370
|
+
## Dialogs and messages
|
|
371
|
+
|
|
372
|
+
Each dialog returns its answer; the window keeps working while your code waits:
|
|
373
|
+
|
|
374
|
+
```python
|
|
375
|
+
from ozdenui import Dialog, Toast
|
|
376
|
+
|
|
377
|
+
if Dialog.confirm("The file will be deleted.", title="Delete it?", confirm_text="Delete", danger=True):
|
|
378
|
+
...
|
|
379
|
+
name = Dialog.prompt("A name for the folder:", value="Photos") # the text, or None
|
|
380
|
+
choice = Dialog.ask("Save the changes?", buttons=["Save", "Don't save", "Cancel"])
|
|
381
|
+
Dialog.alert("Done.")
|
|
382
|
+
Toast.success("Saved.") # also Toast.error / warning / info
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## The app
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
app = App(start, title="Tool", size=(800, 600), components=()) # page.app is the same App
|
|
389
|
+
app.run() # opens the window; returns when it is closed (call it from the main thread)
|
|
390
|
+
app.set_title("New title"); app.close()
|
|
391
|
+
|
|
392
|
+
from ozdenui import every, after, run_in_background, call_soon, open_file, save_file, choose_folder
|
|
393
|
+
|
|
394
|
+
every(1000, tick) # again and again (the loop() of basic.js); returns a function that stops it
|
|
395
|
+
after(500, later) # once; returns a function that cancels it
|
|
396
|
+
run_in_background(job, x) # a job on a thread of its own; it may change objects (a progress bar)
|
|
397
|
+
call_soon(task) # task() on the app thread, from any thread
|
|
398
|
+
open_file(file_types=("Images (*.png;*.jpg)",)) # also save_file(), choose_folder(): a path or None
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
They work in any function of the program, so a button gives its function nothing: `SmartButton(text="Open",
|
|
402
|
+
on_click=open_picture)`. The app has them too (`app.every(...)`).
|
|
403
|
+
|
|
404
|
+
**Threads:** `start`, the events and the timers run one at a time, on one thread: no locks are needed. A job of
|
|
405
|
+
`run_in_background` may change objects; it may not `measure()` (that is refused with a clear message).
|
|
406
|
+
|
|
407
|
+
**A dialog in `start()`** works: the window is shown first. If the page is loaded again (the reload of
|
|
408
|
+
`debug=True`), the timers stop and `start()` runs again.
|
|
409
|
+
|
|
410
|
+
## Examples
|
|
411
|
+
|
|
412
|
+
In the `examples/` folder of the source: `basics` (a Box, an Icon, a Label, an Input and a Button, each in a place of its own), `calculator` (a GridGroup), `counter`,
|
|
413
|
+
`greeting` (a TextField), `gallery` (every component; its Dark / Light button: `page.app.set_theme()`), `gallery-styled` (the gallery with some looks and a color chosen by the code: `style_name`, `style`, `track_color`), `form` (fields, a date, a time,
|
|
414
|
+
a color, a number), `charts` (a live line, bars, a doughnut), `files` (a tree, a menu and files dropped with their paths),
|
|
415
|
+
`file_tool` (a file dialog, a background job and a progress bar), `csv_viewer` (a CSV in a SmartTable, 100000 rows too),
|
|
416
|
+
`todo` (an ItemList; the program is a class), `settings` (Tabs with a form), `dialogs`, `clock` (timers), `game` (places of their own and keys),
|
|
417
|
+
`image_viewer` (a plot drawn again and again), `job_log` (`print()` and `logging` in a LogView) `editor` (a small code
|
|
418
|
+
editor that runs its Python) `markdown_editor` (write on the left, read on the right), `json_viewer` (the path of a row, right click to copy),
|
|
419
|
+
`json_editor` (the products of a shop in a JsonEditor, the settings in its window), `chat` (a ChatView whose answer comes word by
|
|
420
|
+
word from a background job, with Stop), `photos`
|
|
421
|
+
(the pictures of a folder in an ItemList gallery, one of them large in an ImageView), `paint` (a Canvas: a pen, a shape to drag, the drawing saved as a picture)
|
|
422
|
+
`minimal_theme` (every component in its minimal look: `App(style="minimal")`) and `minimal_accent` (the minimal look
|
|
423
|
+
in one color: `App(style="minimal", accent="#2A78D6")`).
|
|
424
|
+
|
|
425
|
+
## Limits of v0.1
|
|
426
|
+
|
|
427
|
+
- Tested only on macOS. Windows and Linux are not tested yet.
|
|
428
|
+
- A callback that must answer at once can not be a Python function, because the answer comes later: the
|
|
429
|
+
`on_before_click` of a SmartButton, an `on_close` of Tabs that returns `False`, the `validate` of a TagInput, and functions
|
|
430
|
+
that build objects inside a component (`createItemView` of a list, `createCellView`). Format the values of a SmartTable in Python;
|
|
431
|
+
a number of a `GraphBox` or a `Stepper` takes a format text. The same for the `color_of` and `options_of` of a JsonEditor
|
|
432
|
+
(its `columns` give the choices), and its `on_pick_color` and `on_pick_picture`: their cell is written with a function of
|
|
433
|
+
the page. Without them a color cell opens the color picker of the web view, and a picture cell takes its address as a text.
|
|
434
|
+
- `set_theme()` is in fifteen components (see "Light and dark"); the others take their look when they are made, so
|
|
435
|
+
`page.app.set_theme()` changes a whole window by making its page again: its objects start again (a text typed in a
|
|
436
|
+
field is gone), what the program keeps in its own variables stays.
|
|
437
|
+
- No browser or Jupyter mode yet.
|
|
438
|
+
|
|
439
|
+
## Development
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
python3 -m venv .venv && .venv/bin/python -m pip install --upgrade pip && .venv/bin/python -m pip install -e . pytest build pillow mypy
|
|
443
|
+
.venv/bin/python -m pytest tests # the Python side, without a window
|
|
444
|
+
.venv/bin/python checks/run_all.py # every example in its real window, played as a user would; a problem names
|
|
445
|
+
# its line of the play (a page error: the step it came after)
|
|
446
|
+
.venv/bin/python checks/check_handbook.py pictures # every program of the handbook in its window; pictures/sheet.png
|
|
447
|
+
.venv/bin/python checks/look.py gallery chat.png 1.5 1000 860 --run "scroll_to(first(ChatView))" --clip "first(ChatView)"
|
|
448
|
+
# a picture of an example: --run code before it (first(), scroll_to(), click(),
|
|
449
|
+
# js()), --clip only the part of these objects
|
|
450
|
+
.venv/bin/python bench/bench.py # the measurements (bench/results.json)
|
|
451
|
+
BASIC_REPO=path/to/js-components ./update-web.sh # copies basic.js and comp/ from that repository again
|
|
452
|
+
./update-web.sh --commit HEAD # the same, as they are in a commit (not the changes of the folder)
|
|
453
|
+
.venv/bin/python make_stubs.py # ozdenui/__init__.pyi again (update-web.sh runs it)
|
|
454
|
+
PYWEBVIEW_GUI=qt python checks/run_all.py # the same check in Chromium, on a Mac (needs PyQt6, PyQt6-WebEngine, qtpy)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
After a new copy of the kit, `tests/test_kit_names.py` names every param, method, state and key of an item that the kit
|
|
458
|
+
does not have any more: the page takes a name it does not know without a word. `ozdenui/__init__.pyi` (the stubs) gives
|
|
459
|
+
an editor the params of every class, from the same lists: `tests/test_stubs.py` fails while it is old.
|
|
460
|
+
`tests/test_types.py` reads the examples and the handbook with mypy and the stubs, as an editor would (skipped without
|
|
461
|
+
mypy). `tests/test_headers.py`: every code file
|
|
462
|
+
has its header (Bismillah, its name and version, the license block); an example has only Bismillah.
|
|
463
|
+
|
|
464
|
+
On Windows, `checks\run_windows.bat` makes the environment and runs all of it; its result is in `checks\windows-result.txt`.
|
|
465
|
+
|
|
466
|
+
## License
|
|
467
|
+
|
|
468
|
+
Copyright 2020-2026 Bugra Ozden <bugra.ozden@gmail.com> (https://bug7a.github.io/)
|
|
469
|
+
Licensed under the Apache License, Version 2.0 (see `LICENSE` and `NOTICE`), the same as basic.js.
|
|
470
|
+
|
|
471
|
+
Developed with the help of Claude Code (Anthropic).
|