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.
Files changed (209) hide show
  1. ozdenui-0.1.0/LICENSE +14 -0
  2. ozdenui-0.1.0/MANIFEST.in +9 -0
  3. ozdenui-0.1.0/NOTICE +5 -0
  4. ozdenui-0.1.0/PKG-INFO +471 -0
  5. ozdenui-0.1.0/README.md +439 -0
  6. ozdenui-0.1.0/checks/check_example.py +1310 -0
  7. ozdenui-0.1.0/checks/check_handbook.py +226 -0
  8. ozdenui-0.1.0/checks/look.py +504 -0
  9. ozdenui-0.1.0/checks/run_all.py +69 -0
  10. ozdenui-0.1.0/checks/run_windows.bat +34 -0
  11. ozdenui-0.1.0/checks/window_check.py +296 -0
  12. ozdenui-0.1.0/examples/basics.py +36 -0
  13. ozdenui-0.1.0/examples/calculator.py +95 -0
  14. ozdenui-0.1.0/examples/charts.py +75 -0
  15. ozdenui-0.1.0/examples/chat.py +84 -0
  16. ozdenui-0.1.0/examples/clock.py +63 -0
  17. ozdenui-0.1.0/examples/counter.py +25 -0
  18. ozdenui-0.1.0/examples/csv_viewer.py +77 -0
  19. ozdenui-0.1.0/examples/dialogs.py +49 -0
  20. ozdenui-0.1.0/examples/editor.py +124 -0
  21. ozdenui-0.1.0/examples/file_tool.py +61 -0
  22. ozdenui-0.1.0/examples/files.py +122 -0
  23. ozdenui-0.1.0/examples/form.py +90 -0
  24. ozdenui-0.1.0/examples/gallery-styled.py +401 -0
  25. ozdenui-0.1.0/examples/gallery.py +337 -0
  26. ozdenui-0.1.0/examples/game.py +94 -0
  27. ozdenui-0.1.0/examples/greeting.py +25 -0
  28. ozdenui-0.1.0/examples/image_viewer.py +88 -0
  29. ozdenui-0.1.0/examples/job_log.py +66 -0
  30. ozdenui-0.1.0/examples/json_editor.py +99 -0
  31. ozdenui-0.1.0/examples/json_viewer.py +63 -0
  32. ozdenui-0.1.0/examples/markdown_editor.py +98 -0
  33. ozdenui-0.1.0/examples/minimal_accent.py +109 -0
  34. ozdenui-0.1.0/examples/minimal_theme.py +234 -0
  35. ozdenui-0.1.0/examples/paint.py +106 -0
  36. ozdenui-0.1.0/examples/photos.py +118 -0
  37. ozdenui-0.1.0/examples/settings.py +79 -0
  38. ozdenui-0.1.0/examples/todo.py +64 -0
  39. ozdenui-0.1.0/handbook/01-first-window.md +119 -0
  40. ozdenui-0.1.0/handbook/02-objects.md +126 -0
  41. ozdenui-0.1.0/handbook/03-layout.md +127 -0
  42. ozdenui-0.1.0/handbook/04-events-and-time.md +154 -0
  43. ozdenui-0.1.0/handbook/05-components.md +229 -0
  44. ozdenui-0.1.0/handbook/06-dialogs-and-files.md +134 -0
  45. ozdenui-0.1.0/handbook/07-looks.md +128 -0
  46. ozdenui-0.1.0/handbook/08-data-and-pictures.md +134 -0
  47. ozdenui-0.1.0/handbook/09-your-own-component.md +132 -0
  48. ozdenui-0.1.0/handbook/10-going-further.md +93 -0
  49. ozdenui-0.1.0/handbook/README.md +53 -0
  50. ozdenui-0.1.0/handbook/turkce/01-first-window.md +122 -0
  51. ozdenui-0.1.0/handbook/turkce/02-objects.md +128 -0
  52. ozdenui-0.1.0/handbook/turkce/03-layout.md +130 -0
  53. ozdenui-0.1.0/handbook/turkce/04-events-and-time.md +154 -0
  54. ozdenui-0.1.0/handbook/turkce/05-components.md +231 -0
  55. ozdenui-0.1.0/handbook/turkce/06-dialogs-and-files.md +139 -0
  56. ozdenui-0.1.0/handbook/turkce/07-looks.md +129 -0
  57. ozdenui-0.1.0/handbook/turkce/08-data-and-pictures.md +139 -0
  58. ozdenui-0.1.0/handbook/turkce/09-your-own-component.md +138 -0
  59. ozdenui-0.1.0/handbook/turkce/10-going-further.md +96 -0
  60. ozdenui-0.1.0/handbook/turkce/README.md +56 -0
  61. ozdenui-0.1.0/make_stubs.py +310 -0
  62. ozdenui-0.1.0/ozdenui/__init__.py +40 -0
  63. ozdenui-0.1.0/ozdenui/__init__.pyi +6614 -0
  64. ozdenui-0.1.0/ozdenui/_kit.py +126 -0
  65. ozdenui-0.1.0/ozdenui/app.py +851 -0
  66. ozdenui-0.1.0/ozdenui/components.py +3972 -0
  67. ozdenui-0.1.0/ozdenui/objects.py +770 -0
  68. ozdenui-0.1.0/ozdenui/py.typed +0 -0
  69. ozdenui-0.1.0/ozdenui/values.py +186 -0
  70. ozdenui-0.1.0/ozdenui/web/basic/LICENSE +14 -0
  71. ozdenui-0.1.0/ozdenui/web/basic/basic.min.css +1 -0
  72. ozdenui-0.1.0/ozdenui/web/basic/basic.min.js +78 -0
  73. ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/LICENSE.txt +206 -0
  74. ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/OpenSans-Bold.ttf +0 -0
  75. ozdenui-0.1.0/ozdenui/web/basic/font/open-sans/OpenSans-Regular.ttf +0 -0
  76. ozdenui-0.1.0/ozdenui/web/basic/scroll-bar.min.js +3 -0
  77. ozdenui-0.1.0/ozdenui/web/bridge.js +526 -0
  78. ozdenui-0.1.0/ozdenui/web/comp/accordion.js +659 -0
  79. ozdenui-0.1.0/ozdenui/web/comp/action-button/add.png +0 -0
  80. ozdenui-0.1.0/ozdenui/web/comp/action-button.js +521 -0
  81. ozdenui-0.1.0/ozdenui/web/comp/action-tab-bar.js +383 -0
  82. ozdenui-0.1.0/ozdenui/web/comp/app-bar.js +237 -0
  83. ozdenui-0.1.0/ozdenui/web/comp/badge.js +292 -0
  84. ozdenui-0.1.0/ozdenui/web/comp/bottom-bar.js +555 -0
  85. ozdenui-0.1.0/ozdenui/web/comp/breadcrumbs.js +524 -0
  86. ozdenui-0.1.0/ozdenui/web/comp/canvas.js +652 -0
  87. ozdenui-0.1.0/ozdenui/web/comp/chat-view.js +827 -0
  88. ozdenui-0.1.0/ozdenui/web/comp/check-box.js +490 -0
  89. ozdenui-0.1.0/ozdenui/web/comp/code-editor.js +953 -0
  90. ozdenui-0.1.0/ozdenui/web/comp/color-picker.js +636 -0
  91. ozdenui-0.1.0/ozdenui/web/comp/context-menu.js +946 -0
  92. ozdenui-0.1.0/ozdenui/web/comp/currency-field.js +209 -0
  93. ozdenui-0.1.0/ozdenui/web/comp/data-table/arrow.svg +2 -0
  94. ozdenui-0.1.0/ozdenui/web/comp/data-table.js +937 -0
  95. ozdenui-0.1.0/ozdenui/web/comp/date-picker.js +1055 -0
  96. ozdenui-0.1.0/ozdenui/web/comp/dialog.js +992 -0
  97. ozdenui-0.1.0/ozdenui/web/comp/email-field.js +65 -0
  98. ozdenui-0.1.0/ozdenui/web/comp/file-picker.js +920 -0
  99. ozdenui-0.1.0/ozdenui/web/comp/filters.js +807 -0
  100. ozdenui-0.1.0/ozdenui/web/comp/form.js +602 -0
  101. ozdenui-0.1.0/ozdenui/web/comp/full-screen-select/arrow.svg +2 -0
  102. ozdenui-0.1.0/ozdenui/web/comp/full-screen-select/cancel.svg +2 -0
  103. ozdenui-0.1.0/ozdenui/web/comp/full-screen-select.js +614 -0
  104. ozdenui-0.1.0/ozdenui/web/comp/gauge.js +439 -0
  105. ozdenui-0.1.0/ozdenui/web/comp/graph-box.js +3911 -0
  106. ozdenui-0.1.0/ozdenui/web/comp/grid-group.js +185 -0
  107. ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button/trash-red.png +0 -0
  108. ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button/trash.png +0 -0
  109. ozdenui-0.1.0/ozdenui/web/comp/hold-to-confirm-button.js +289 -0
  110. ozdenui-0.1.0/ozdenui/web/comp/image-view.js +598 -0
  111. ozdenui-0.1.0/ozdenui/web/comp/item-list.js +1740 -0
  112. ozdenui-0.1.0/ozdenui/web/comp/json-editor.js +3221 -0
  113. ozdenui-0.1.0/ozdenui/web/comp/json-view.js +597 -0
  114. ozdenui-0.1.0/ozdenui/web/comp/label-editor.js +1140 -0
  115. ozdenui-0.1.0/ozdenui/web/comp/left-menu/data-table.png +0 -0
  116. ozdenui-0.1.0/ozdenui/web/comp/left-menu/home.png +0 -0
  117. ozdenui-0.1.0/ozdenui/web/comp/left-menu/reports.png +0 -0
  118. ozdenui-0.1.0/ozdenui/web/comp/left-menu/setting.png +0 -0
  119. ozdenui-0.1.0/ozdenui/web/comp/left-menu.js +548 -0
  120. ozdenui-0.1.0/ozdenui/web/comp/line-progress-bar.js +343 -0
  121. ozdenui-0.1.0/ozdenui/web/comp/loading-screen.js +413 -0
  122. ozdenui-0.1.0/ozdenui/web/comp/log-view.js +530 -0
  123. ozdenui-0.1.0/ozdenui/web/comp/login-page.js +685 -0
  124. ozdenui-0.1.0/ozdenui/web/comp/markdown-view.js +1044 -0
  125. ozdenui-0.1.0/ozdenui/web/comp/menu-bar.js +624 -0
  126. ozdenui-0.1.0/ozdenui/web/comp/mini-graph-box.js +492 -0
  127. ozdenui-0.1.0/ozdenui/web/comp/minimal-accent.js +208 -0
  128. ozdenui-0.1.0/ozdenui/web/comp/notice.js +229 -0
  129. ozdenui-0.1.0/ozdenui/web/comp/number-field.js +128 -0
  130. ozdenui-0.1.0/ozdenui/web/comp/numeric-keyboard.js +298 -0
  131. ozdenui-0.1.0/ozdenui/web/comp/page-control.js +762 -0
  132. ozdenui-0.1.0/ozdenui/web/comp/page-header.js +191 -0
  133. ozdenui-0.1.0/ozdenui/web/comp/password-field/hide-btn.png +0 -0
  134. ozdenui-0.1.0/ozdenui/web/comp/password-field/hide-btn2.png +0 -0
  135. ozdenui-0.1.0/ozdenui/web/comp/password-field/show-btn.png +0 -0
  136. ozdenui-0.1.0/ozdenui/web/comp/password-field/show-btn2.png +0 -0
  137. ozdenui-0.1.0/ozdenui/web/comp/password-field.js +192 -0
  138. ozdenui-0.1.0/ozdenui/web/comp/period-bar.js +436 -0
  139. ozdenui-0.1.0/ozdenui/web/comp/phone-field.js +322 -0
  140. ozdenui-0.1.0/ozdenui/web/comp/progress-bar.js +992 -0
  141. ozdenui-0.1.0/ozdenui/web/comp/property-panel.js +3639 -0
  142. ozdenui-0.1.0/ozdenui/web/comp/radio-button.js +527 -0
  143. ozdenui-0.1.0/ozdenui/web/comp/range-slider.js +569 -0
  144. ozdenui-0.1.0/ozdenui/web/comp/rich-text-editor.js +1064 -0
  145. ozdenui-0.1.0/ozdenui/web/comp/scroll-area.js +308 -0
  146. ozdenui-0.1.0/ozdenui/web/comp/scrolling-text.js +297 -0
  147. ozdenui-0.1.0/ozdenui/web/comp/search-input/clear-round.svg +1 -0
  148. ozdenui-0.1.0/ozdenui/web/comp/search-input/clear.svg +1 -0
  149. ozdenui-0.1.0/ozdenui/web/comp/search-input/filter.png +0 -0
  150. ozdenui-0.1.0/ozdenui/web/comp/search-input/filter.svg +1 -0
  151. ozdenui-0.1.0/ozdenui/web/comp/search-input/search.svg +1 -0
  152. ozdenui-0.1.0/ozdenui/web/comp/search-input.js +725 -0
  153. ozdenui-0.1.0/ozdenui/web/comp/search-results.js +596 -0
  154. ozdenui-0.1.0/ozdenui/web/comp/select-box.js +1862 -0
  155. ozdenui-0.1.0/ozdenui/web/comp/settings-row.js +299 -0
  156. ozdenui-0.1.0/ozdenui/web/comp/side-panel.js +616 -0
  157. ozdenui-0.1.0/ozdenui/web/comp/slider-field.js +952 -0
  158. ozdenui-0.1.0/ozdenui/web/comp/slider.js +838 -0
  159. ozdenui-0.1.0/ozdenui/web/comp/smart-button/alert-black.png +0 -0
  160. ozdenui-0.1.0/ozdenui/web/comp/smart-button/clock.png +0 -0
  161. ozdenui-0.1.0/ozdenui/web/comp/smart-button.js +981 -0
  162. ozdenui-0.1.0/ozdenui/web/comp/smart-table/clock.png +0 -0
  163. ozdenui-0.1.0/ozdenui/web/comp/smart-table/down.png +0 -0
  164. ozdenui-0.1.0/ozdenui/web/comp/smart-table/scroll.png +0 -0
  165. ozdenui-0.1.0/ozdenui/web/comp/smart-table/sort.png +0 -0
  166. ozdenui-0.1.0/ozdenui/web/comp/smart-table/up.png +0 -0
  167. ozdenui-0.1.0/ozdenui/web/comp/smart-table.js +1876 -0
  168. ozdenui-0.1.0/ozdenui/web/comp/sortable-list.js +1181 -0
  169. ozdenui-0.1.0/ozdenui/web/comp/spark-line-box.js +738 -0
  170. ozdenui-0.1.0/ozdenui/web/comp/splitter.js +411 -0
  171. ozdenui-0.1.0/ozdenui/web/comp/status-bar.js +408 -0
  172. ozdenui-0.1.0/ozdenui/web/comp/step-bar.js +345 -0
  173. ozdenui-0.1.0/ozdenui/web/comp/stepper.js +753 -0
  174. ozdenui-0.1.0/ozdenui/web/comp/tabs.js +1366 -0
  175. ozdenui-0.1.0/ozdenui/web/comp/tag-input.js +1107 -0
  176. ozdenui-0.1.0/ozdenui/web/comp/tap-effect.js +170 -0
  177. ozdenui-0.1.0/ozdenui/web/comp/text-area-field.js +233 -0
  178. ozdenui-0.1.0/ozdenui/web/comp/text-field.js +1006 -0
  179. ozdenui-0.1.0/ozdenui/web/comp/time-line.js +398 -0
  180. ozdenui-0.1.0/ozdenui/web/comp/time-picker.js +1032 -0
  181. ozdenui-0.1.0/ozdenui/web/comp/tiny-select/arrow.svg +1 -0
  182. ozdenui-0.1.0/ozdenui/web/comp/tiny-select.js +322 -0
  183. ozdenui-0.1.0/ozdenui/web/comp/tiny-table.js +283 -0
  184. ozdenui-0.1.0/ozdenui/web/comp/toast.js +891 -0
  185. ozdenui-0.1.0/ozdenui/web/comp/toggle.js +757 -0
  186. ozdenui-0.1.0/ozdenui/web/comp/tool-bar.js +760 -0
  187. ozdenui-0.1.0/ozdenui/web/comp/tooltip.js +534 -0
  188. ozdenui-0.1.0/ozdenui/web/comp/tree-view.js +1179 -0
  189. ozdenui-0.1.0/ozdenui/web/comp/ui-effects.js +1097 -0
  190. ozdenui-0.1.0/ozdenui/web/comp/ui-standards.js +403 -0
  191. ozdenui-0.1.0/ozdenui/web/comp/url-field.js +190 -0
  192. ozdenui-0.1.0/ozdenui/web/comp/web-view.js +588 -0
  193. ozdenui-0.1.0/ozdenui/web/components.js +122 -0
  194. ozdenui-0.1.0/ozdenui/web/index.html +96 -0
  195. ozdenui-0.1.0/ozdenui.egg-info/PKG-INFO +471 -0
  196. ozdenui-0.1.0/ozdenui.egg-info/SOURCES.txt +207 -0
  197. ozdenui-0.1.0/ozdenui.egg-info/dependency_links.txt +1 -0
  198. ozdenui-0.1.0/ozdenui.egg-info/requires.txt +1 -0
  199. ozdenui-0.1.0/ozdenui.egg-info/top_level.txt +1 -0
  200. ozdenui-0.1.0/pyproject.toml +50 -0
  201. ozdenui-0.1.0/setup.cfg +4 -0
  202. ozdenui-0.1.0/tests/bridge_harness.js +79 -0
  203. ozdenui-0.1.0/tests/test_bridge.py +146 -0
  204. ozdenui-0.1.0/tests/test_handbook.py +74 -0
  205. ozdenui-0.1.0/tests/test_headers.py +133 -0
  206. ozdenui-0.1.0/tests/test_kit_names.py +378 -0
  207. ozdenui-0.1.0/tests/test_ozdenui.py +2942 -0
  208. ozdenui-0.1.0/tests/test_stubs.py +103 -0
  209. 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
@@ -0,0 +1,5 @@
1
+ OzdenUI (basic.js for Python)
2
+ Copyright 2020-2026 Bugra Ozden <bugra.ozden@gmail.com> (https://bug7a.github.io/)
3
+
4
+ This product includes software developed by Bugra Ozden
5
+ (https://bug7a.github.io/basic.js/).
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. `![A plot](plot.png)` reads a file of this
222
+ computer (`picture_folder=`, or next to the file of `load_file()`); a matplotlib figure: `f"![A plot]({picture_address(figure)})"`.
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).