pgwidgets-python 0.1.3__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 (42) hide show
  1. pgwidgets_python-0.1.3/.flake8 +3 -0
  2. pgwidgets_python-0.1.3/.gitignore +12 -0
  3. pgwidgets_python-0.1.3/.readthedocs.yaml +19 -0
  4. pgwidgets_python-0.1.3/LICENSE.md +29 -0
  5. pgwidgets_python-0.1.3/PKG-INFO +121 -0
  6. pgwidgets_python-0.1.3/README.md +92 -0
  7. pgwidgets_python-0.1.3/docs/Makefile +15 -0
  8. pgwidgets_python-0.1.3/docs/api/async.rst +27 -0
  9. pgwidgets_python-0.1.3/docs/api/index.rst +10 -0
  10. pgwidgets_python-0.1.3/docs/api/sync.rst +27 -0
  11. pgwidgets_python-0.1.3/docs/architecture.rst +94 -0
  12. pgwidgets_python-0.1.3/docs/async.rst +152 -0
  13. pgwidgets_python-0.1.3/docs/callbacks.rst +213 -0
  14. pgwidgets_python-0.1.3/docs/conf.py +32 -0
  15. pgwidgets_python-0.1.3/docs/getting-started.rst +95 -0
  16. pgwidgets_python-0.1.3/docs/index.rst +49 -0
  17. pgwidgets_python-0.1.3/docs/sync.rst +190 -0
  18. pgwidgets_python-0.1.3/docs/web-servers.rst +370 -0
  19. pgwidgets_python-0.1.3/docs/widgets.rst +579 -0
  20. pgwidgets_python-0.1.3/examples/README.md +24 -0
  21. pgwidgets_python-0.1.3/examples/demo_async.py +79 -0
  22. pgwidgets_python-0.1.3/examples/demo_sync.py +87 -0
  23. pgwidgets_python-0.1.3/examples/demo_treeview.py +94 -0
  24. pgwidgets_python-0.1.3/pgwidgets/__init__.py +19 -0
  25. pgwidgets_python-0.1.3/pgwidgets/async_/__init__.py +24 -0
  26. pgwidgets_python-0.1.3/pgwidgets/async_/application.py +660 -0
  27. pgwidgets_python-0.1.3/pgwidgets/async_/widget.py +171 -0
  28. pgwidgets_python-0.1.3/pgwidgets/defs.py +11 -0
  29. pgwidgets_python-0.1.3/pgwidgets/sync/__init__.py +25 -0
  30. pgwidgets_python-0.1.3/pgwidgets/sync/application.py +796 -0
  31. pgwidgets_python-0.1.3/pgwidgets/sync/widget.py +166 -0
  32. pgwidgets_python-0.1.3/pgwidgets_python.egg-info/PKG-INFO +121 -0
  33. pgwidgets_python-0.1.3/pgwidgets_python.egg-info/SOURCES.txt +40 -0
  34. pgwidgets_python-0.1.3/pgwidgets_python.egg-info/dependency_links.txt +1 -0
  35. pgwidgets_python-0.1.3/pgwidgets_python.egg-info/requires.txt +10 -0
  36. pgwidgets_python-0.1.3/pgwidgets_python.egg-info/top_level.txt +1 -0
  37. pgwidgets_python-0.1.3/pyproject.toml +44 -0
  38. pgwidgets_python-0.1.3/setup.cfg +4 -0
  39. pgwidgets_python-0.1.3/tests/__init__.py +0 -0
  40. pgwidgets_python-0.1.3/tests/test_defs.py +115 -0
  41. pgwidgets_python-0.1.3/tests/test_protocol.py +245 -0
  42. pgwidgets_python-0.1.3/tests/test_widget_classes.py +102 -0
@@ -0,0 +1,3 @@
1
+ [flake8]
2
+ max-line-length = 100
3
+ extend-ignore = E501
@@ -0,0 +1,12 @@
1
+ __pycache__
2
+ *.py[co]
3
+ *.egg
4
+ *.egg-info
5
+ dist
6
+ build
7
+ docs/_build
8
+ node_modules
9
+ .DS_Store
10
+ *~
11
+ \#*\#
12
+ .*.swp
@@ -0,0 +1,19 @@
1
+ # Read the Docs configuration file
2
+ # See https://docs.readthedocs.io/en/stable/config-file/v2.html
3
+
4
+ version: 2
5
+
6
+ build:
7
+ os: ubuntu-24.04
8
+ tools:
9
+ python: "3.12"
10
+
11
+ sphinx:
12
+ configuration: docs/conf.py
13
+
14
+ python:
15
+ install:
16
+ - method: pip
17
+ path: .
18
+ extra_requirements:
19
+ - dev
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, PGWidgets developers
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: pgwidgets-python
3
+ Version: 0.1.3
4
+ Summary: Python bindings for the pgwidgets JavaScript widget library
5
+ Author: PGWidgets Developers
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/naojsoft/pgwidgets-python
8
+ Project-URL: Repository, https://github.com/naojsoft/pgwidgets-python
9
+ Keywords: widgets,ui,gui,websocket,browser
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: BSD License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Topic :: Software Development :: User Interfaces
17
+ Requires-Python: >=3.12
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE.md
20
+ Requires-Dist: pgwidgets-js
21
+ Requires-Dist: websockets>=12
22
+ Provides-Extra: dev
23
+ Requires-Dist: sphinx; extra == "dev"
24
+ Requires-Dist: furo; extra == "dev"
25
+ Requires-Dist: sphinx-autodoc-typehints; extra == "dev"
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest; extra == "test"
28
+ Dynamic: license-file
29
+
30
+ # pgwidgets — Python Bindings
31
+
32
+ Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
33
+ JavaScript widget library. Build desktop-style browser UIs from Python
34
+ with a familiar Qt/GTK-style API.
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install pgwidgets-python
40
+ ```
41
+
42
+ This will also install `pgwidgets-js` (the JavaScript assets) and
43
+ `websockets` as dependencies.
44
+
45
+ ## Quick Start
46
+
47
+ ```python
48
+ from pgwidgets.sync import Application
49
+
50
+ app = Application()
51
+
52
+ @app.on_connect
53
+ def setup(session):
54
+ Widgets = session.get_widgets()
55
+
56
+ top = Widgets.TopLevel(title="Hello", resizable=True)
57
+ top.resize(400, 300)
58
+
59
+ vbox = Widgets.VBox(spacing=8, padding=10)
60
+ btn = Widgets.Button("Click me")
61
+ label = Widgets.Label("Ready")
62
+
63
+ btn.on("activated", lambda: label.set_text("Clicked!"))
64
+
65
+ vbox.add_widget(btn, 0)
66
+ vbox.add_widget(label, 1)
67
+ top.set_widget(vbox)
68
+ top.show()
69
+
70
+ app.run()
71
+ ```
72
+
73
+ Run the script, then open the printed URL in your browser.
74
+
75
+ ## Sync vs Async
76
+
77
+ Both APIs provide the same widget classes and methods.
78
+
79
+ **Synchronous** (recommended for most use cases):
80
+ ```python
81
+ from pgwidgets.sync import Application
82
+ app = Application()
83
+
84
+ @app.on_connect
85
+ def setup(session):
86
+ Widgets = session.get_widgets()
87
+ btn = Widgets.Button("Click") # blocking call
88
+ btn.set_text("New text") # blocking call
89
+
90
+ app.run()
91
+ ```
92
+
93
+ **Asynchronous** (for asyncio applications):
94
+ ```python
95
+ from pgwidgets.async_ import Application
96
+ app = Application()
97
+
98
+ @app.on_connect
99
+ async def setup(session):
100
+ Widgets = session.get_widgets()
101
+ btn = await Widgets.Button("Click") # awaitable
102
+ await btn.set_text("New text") # awaitable
103
+
104
+ await app.run()
105
+ ```
106
+
107
+ ## How It Works
108
+
109
+ The `Application` class starts two servers:
110
+ - An **HTTP server** (default port 9501) that serves the pgwidgets JS/CSS
111
+ and a connector page
112
+ - A **WebSocket server** (default port 9500) for the JSON command protocol
113
+
114
+ When you open the URL in a browser, the page loads pgwidgets and connects
115
+ back over WebSocket. Python widget constructors and method calls are
116
+ translated to JSON messages and executed in the browser. Callbacks are
117
+ forwarded back to Python.
118
+
119
+ ## License
120
+
121
+ BSD 3-Clause
@@ -0,0 +1,92 @@
1
+ # pgwidgets — Python Bindings
2
+
3
+ Python bindings for the [pgwidgets](https://github.com/naojsoft/pgwidgets-js)
4
+ JavaScript widget library. Build desktop-style browser UIs from Python
5
+ with a familiar Qt/GTK-style API.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install pgwidgets-python
11
+ ```
12
+
13
+ This will also install `pgwidgets-js` (the JavaScript assets) and
14
+ `websockets` as dependencies.
15
+
16
+ ## Quick Start
17
+
18
+ ```python
19
+ from pgwidgets.sync import Application
20
+
21
+ app = Application()
22
+
23
+ @app.on_connect
24
+ def setup(session):
25
+ Widgets = session.get_widgets()
26
+
27
+ top = Widgets.TopLevel(title="Hello", resizable=True)
28
+ top.resize(400, 300)
29
+
30
+ vbox = Widgets.VBox(spacing=8, padding=10)
31
+ btn = Widgets.Button("Click me")
32
+ label = Widgets.Label("Ready")
33
+
34
+ btn.on("activated", lambda: label.set_text("Clicked!"))
35
+
36
+ vbox.add_widget(btn, 0)
37
+ vbox.add_widget(label, 1)
38
+ top.set_widget(vbox)
39
+ top.show()
40
+
41
+ app.run()
42
+ ```
43
+
44
+ Run the script, then open the printed URL in your browser.
45
+
46
+ ## Sync vs Async
47
+
48
+ Both APIs provide the same widget classes and methods.
49
+
50
+ **Synchronous** (recommended for most use cases):
51
+ ```python
52
+ from pgwidgets.sync import Application
53
+ app = Application()
54
+
55
+ @app.on_connect
56
+ def setup(session):
57
+ Widgets = session.get_widgets()
58
+ btn = Widgets.Button("Click") # blocking call
59
+ btn.set_text("New text") # blocking call
60
+
61
+ app.run()
62
+ ```
63
+
64
+ **Asynchronous** (for asyncio applications):
65
+ ```python
66
+ from pgwidgets.async_ import Application
67
+ app = Application()
68
+
69
+ @app.on_connect
70
+ async def setup(session):
71
+ Widgets = session.get_widgets()
72
+ btn = await Widgets.Button("Click") # awaitable
73
+ await btn.set_text("New text") # awaitable
74
+
75
+ await app.run()
76
+ ```
77
+
78
+ ## How It Works
79
+
80
+ The `Application` class starts two servers:
81
+ - An **HTTP server** (default port 9501) that serves the pgwidgets JS/CSS
82
+ and a connector page
83
+ - A **WebSocket server** (default port 9500) for the JSON command protocol
84
+
85
+ When you open the URL in a browser, the page loads pgwidgets and connects
86
+ back over WebSocket. Python widget constructors and method calls are
87
+ translated to JSON messages and executed in the browser. Callbacks are
88
+ forwarded back to Python.
89
+
90
+ ## License
91
+
92
+ BSD 3-Clause
@@ -0,0 +1,15 @@
1
+ # Minimal makefile for Sphinx documentation
2
+
3
+ SPHINXOPTS ?=
4
+ SPHINXBUILD ?= sphinx-build
5
+ SOURCEDIR = .
6
+ BUILDDIR = _build
7
+
8
+ help:
9
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
10
+
11
+ .PHONY: help Makefile
12
+
13
+ # Catch-all target: route all unknown targets to Sphinx
14
+ %: Makefile
15
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
@@ -0,0 +1,27 @@
1
+ pgwidgets.async\_
2
+ =================
3
+
4
+ .. module:: pgwidgets.async_
5
+
6
+ Asynchronous API for pgwidgets.
7
+
8
+ Application
9
+ -----------
10
+
11
+ .. autoclass:: pgwidgets.async_.application.Application
12
+ :members:
13
+ :show-inheritance:
14
+
15
+ Session
16
+ -------
17
+
18
+ .. autoclass:: pgwidgets.async_.application.Session
19
+ :members:
20
+ :show-inheritance:
21
+
22
+ Widget
23
+ ------
24
+
25
+ .. autoclass:: pgwidgets.async_.widget.Widget
26
+ :members:
27
+ :show-inheritance:
@@ -0,0 +1,10 @@
1
+ API Reference
2
+ =============
3
+
4
+ Auto-generated API documentation from source docstrings.
5
+
6
+ .. toctree::
7
+ :maxdepth: 2
8
+
9
+ sync
10
+ async
@@ -0,0 +1,27 @@
1
+ pgwidgets.sync
2
+ ==============
3
+
4
+ .. module:: pgwidgets.sync
5
+
6
+ Synchronous API for pgwidgets.
7
+
8
+ Application
9
+ -----------
10
+
11
+ .. autoclass:: pgwidgets.sync.application.Application
12
+ :members:
13
+ :show-inheritance:
14
+
15
+ Session
16
+ -------
17
+
18
+ .. autoclass:: pgwidgets.sync.application.Session
19
+ :members:
20
+ :show-inheritance:
21
+
22
+ Widget
23
+ ------
24
+
25
+ .. autoclass:: pgwidgets.sync.widget.Widget
26
+ :members:
27
+ :show-inheritance:
@@ -0,0 +1,94 @@
1
+ Architecture
2
+ ============
3
+
4
+ Overview
5
+ --------
6
+
7
+ pgwidgets follows a client-server architecture. Python is the server; the
8
+ browser is the client. Widget constructors and method calls in Python are
9
+ translated to JSON messages and sent over WebSocket to the browser, where the
10
+ pgwidgets JavaScript library executes them. User interactions (clicks, input,
11
+ etc.) travel back as callback messages.
12
+
13
+ ::
14
+
15
+ Python (server) Browser (client)
16
+ +-----------------+ +------------------+
17
+ | Application | WebSocket | pgwidgets JS |
18
+ | Session <----|----JSON------->| widget tree |
19
+ | widgets | | DOM rendering |
20
+ +-----------------+ +------------------+
21
+ |
22
+ | HTTP (static files)
23
+ v
24
+ JS/CSS assets served to browser
25
+
26
+ Servers
27
+ -------
28
+
29
+ The ``Application`` class starts two servers:
30
+
31
+ **HTTP server** (default port 9501)
32
+ Serves the pgwidgets JavaScript/CSS assets and a connector HTML page.
33
+ When a browser hits ``/``, it gets ``remote.html`` with the WebSocket URL
34
+ injected. Set ``http_server=False`` if you serve the static files from
35
+ your own web server (Flask, FastAPI, nginx, etc.).
36
+
37
+ **WebSocket server** (default port 9500)
38
+ Carries the JSON command protocol. Each browser tab opens one WebSocket
39
+ connection, which becomes one ``Session``.
40
+
41
+ JSON Protocol
42
+ -------------
43
+
44
+ All messages are JSON objects with a ``type`` field.
45
+
46
+ **Python -> Browser:**
47
+
48
+ - ``{"type": "init", "id": 0}`` -- reset the browser to a clean slate.
49
+ - ``{"type": "create", "wid": 1, "class": "Button", "args": ["Click"]}`` --
50
+ create a widget.
51
+ - ``{"type": "call", "wid": 1, "method": "set_text", "args": ["New"]}`` --
52
+ call a method on a widget.
53
+ - ``{"type": "listen", "wid": 1, "action": "activated"}`` -- subscribe to a
54
+ callback.
55
+ - ``{"type": "unlisten", "wid": 1, "action": "activated"}`` -- unsubscribe.
56
+
57
+ **Browser -> Python:**
58
+
59
+ - ``{"type": "result", "id": 1, "value": ...}`` -- method return value.
60
+ - ``{"type": "error", "id": 1, "error": "..."}`` -- method error.
61
+ - ``{"type": "callback", "wid": 1, "action": "activated", "args": [...]}`` --
62
+ user interaction.
63
+ - ``{"type": "file-chunk", ...}`` -- chunked file data (see :doc:`callbacks`).
64
+
65
+ Session Model
66
+ -------------
67
+
68
+ Each browser tab that connects gets its own ``Session`` object. The session
69
+ owns:
70
+
71
+ - A widget map (``wid`` -> Python widget wrapper)
72
+ - A callback registry (``"wid:action"`` -> handler function)
73
+ - A message-ID counter for request/response matching
74
+
75
+ Multiple sessions can be active concurrently (controlled by ``max_sessions``).
76
+
77
+ Lifecycle:
78
+
79
+ 1. Browser opens the URL and loads ``remote.html``.
80
+ 2. JavaScript connects to the WebSocket server.
81
+ 3. Python sends ``init``; browser acknowledges.
82
+ 4. ``on_connect`` callback fires with the new ``Session``.
83
+ 5. User code creates widgets, registers callbacks.
84
+ 6. When the browser tab closes, ``on_disconnect`` fires and the session is
85
+ cleaned up.
86
+
87
+ Widget References
88
+ -----------------
89
+
90
+ Widgets are identified by integer IDs (``wid``). When a Python widget is
91
+ passed as an argument to another widget's method (e.g., ``vbox.add_widget(btn,
92
+ 0)``), the framework automatically converts the Python ``Widget`` object to a
93
+ ``{"__wid__": N}`` reference on the wire, and converts it back on return
94
+ values.
@@ -0,0 +1,152 @@
1
+ Asynchronous API
2
+ ================
3
+
4
+ The async API lives in ``pgwidgets.async_``. All widget constructors and
5
+ method calls are coroutines that must be awaited.
6
+
7
+ .. code-block:: python
8
+
9
+ from pgwidgets.async_ import Application
10
+
11
+ Application
12
+ -----------
13
+
14
+ .. code-block:: python
15
+
16
+ app = Application(
17
+ ws_port=9500,
18
+ http_port=9501,
19
+ host="127.0.0.1",
20
+ http_server=True,
21
+ concurrency_handling="per_session",
22
+ max_sessions=1,
23
+ logger=None,
24
+ )
25
+
26
+ The constructor parameters are the same as the sync version (see :doc:`sync`).
27
+
28
+ on_connect / on_disconnect
29
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
30
+
31
+ Handlers can be sync or async:
32
+
33
+ .. code-block:: python
34
+
35
+ @app.on_connect
36
+ async def setup(session):
37
+ Widgets = session.get_widgets()
38
+ top = await Widgets.TopLevel(title="Hello")
39
+ await top.show()
40
+
41
+ @app.on_disconnect
42
+ async def teardown(session):
43
+ print(f"Session {session.id} disconnected")
44
+
45
+ Running
46
+ ~~~~~~~
47
+
48
+ .. code-block:: python
49
+
50
+ # Inside an async context
51
+ await app.run()
52
+
53
+ # Or with asyncio.run()
54
+ import asyncio
55
+ asyncio.run(main())
56
+
57
+ ``await app.close()`` shuts down all sessions and causes ``run()`` to return.
58
+
59
+ Session
60
+ -------
61
+
62
+ The async ``Session`` has the same interface as the sync version, but methods
63
+ are coroutines:
64
+
65
+ .. code-block:: python
66
+
67
+ Widgets = session.get_widgets() # sync -- returns namespace
68
+ btn = await Widgets.Button("Click me") # async -- creates widget
69
+ await btn.set_text("New text") # async -- calls method
70
+ await session.close() # async
71
+ timer = await session.make_timer(duration=1000) # async
72
+
73
+ Concurrency Modes
74
+ -----------------
75
+
76
+ In the async API, concurrency is managed with ``asyncio.Lock`` instead of
77
+ threads:
78
+
79
+ **per_session** (default)
80
+ Each session gets its own ``asyncio.Lock``. Callbacks within a session
81
+ are serialized, but different sessions can interleave at ``await`` points.
82
+
83
+ **serialized**
84
+ All callbacks from all sessions are serialized under a single global
85
+ ``asyncio.Lock``.
86
+
87
+ **concurrent**
88
+ Callbacks are dispatched via ``asyncio.ensure_future`` with no
89
+ serialization.
90
+
91
+ Callbacks
92
+ ~~~~~~~~~
93
+
94
+ Callback handlers can be sync or async. Async handlers are awaited:
95
+
96
+ .. code-block:: python
97
+
98
+ async def on_click():
99
+ await status.set_text("Clicked!")
100
+
101
+ await btn.on("activated", on_click)
102
+
103
+ Full Example
104
+ ------------
105
+
106
+ .. code-block:: python
107
+
108
+ import asyncio
109
+ import logging
110
+ from pgwidgets.async_ import Application
111
+
112
+ logging.basicConfig(level=logging.INFO)
113
+ logger = logging.getLogger("pgwidgets")
114
+
115
+ async def main():
116
+ app = Application(max_sessions=4, logger=logger)
117
+
118
+ @app.on_connect
119
+ async def on_session(session):
120
+ Widgets = session.get_widgets()
121
+
122
+ top = await Widgets.TopLevel(title="Async Demo", resizable=True)
123
+ await top.resize(400, 300)
124
+
125
+ vbox = await Widgets.VBox(spacing=8, padding=10)
126
+ status = await Widgets.Label("Click a button!")
127
+
128
+ hbox = await Widgets.HBox(spacing=6)
129
+ btn = await Widgets.Button("Hello")
130
+ await hbox.add_widget(btn, 0)
131
+
132
+ entry = await Widgets.TextEntry(text="Type here", linehistory=5)
133
+
134
+ await vbox.add_widget(hbox, 0)
135
+ await vbox.add_widget(entry, 0)
136
+ await vbox.add_widget(status, 1)
137
+ await top.set_widget(vbox)
138
+ await top.show()
139
+
140
+ async def on_hello():
141
+ await status.set_text("Hello!")
142
+
143
+ async def on_entry(text):
144
+ await status.set_text(f"Entered: {text}")
145
+
146
+ await btn.on("activated", on_hello)
147
+ await entry.on("activated", on_entry)
148
+
149
+ await app.run()
150
+
151
+ if __name__ == "__main__":
152
+ asyncio.run(main())