{"id":15955649,"url":"https://github.com/buganini/pui","last_synced_at":"2025-04-30T05:20:39.679Z","repository":{"id":141918619,"uuid":"611919157","full_name":"buganini/PUI","owner":"buganini","description":"Python Reactive/Declarative UI Framework. PUI doesn't do UI itself, it turns imperative UI libraries into declarative flavor with virtual DOM and aims to maintain interoperability.","archived":false,"fork":false,"pushed_at":"2025-04-28T22:34:30.000Z","size":6064,"stargazers_count":86,"open_issues_count":1,"forks_count":6,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-28T22:54:12.331Z","etag":null,"topics":["declarative-ui","flet","pyside6","python","qt","textual","tkinter","wxpython"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/buganini.png","metadata":{"files":{"readme":"README.md","changelog":"Changelog.txt","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-03-09T20:15:06.000Z","updated_at":"2025-04-28T22:34:33.000Z","dependencies_parsed_at":"2024-03-30T04:31:44.720Z","dependency_job_id":"922657b2-31fc-4690-b0fa-e2aa4accb821","html_url":"https://github.com/buganini/PUI","commit_stats":null,"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buganini%2FPUI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buganini%2FPUI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buganini%2FPUI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buganini%2FPUI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/buganini","download_url":"https://codeload.github.com/buganini/PUI/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251645732,"owners_count":21620806,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["declarative-ui","flet","pyside6","python","qt","textual","tkinter","wxpython"],"created_at":"2024-10-07T13:25:55.086Z","updated_at":"2025-04-30T05:20:39.673Z","avatar_url":"https://github.com/buganini.png","language":"Python","readme":"# What is PUI\nPUI is a reactive/declarative UI framework with two-way data binding.\nPUI doesn't do UI itself, it turns imperative UI libraries into reactive/declarative flavor with virtual DOM and aims to maintain interoperability.\n\n[Slides for SciWork 2023](https://speakerdeck.com/buganini/pui-declarative-ui-framework-for-python)\n\n[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)\n\n# Installation\n``` shell\npip install QPUIQ\n\n# Optional, for hot-reload\npip install jurigged\n\n# Optional, for PySide6 backend\npip install PySide6\n\n# Optional, for textual backend\npip install textual\n\n# Optional, for wxPython backend\npip install wxPython\n\n# Optional, for flet backend\npip install flet\n```\n\n# Get Started\n## Hello World\n```python\n# example/hello_world.py\nfrom PUI.PySide6 import *\n\n@PUIApp\ndef Example():\n    with Window(title=\"test\", size=(320,240)):\n        Label(\"Hello world\")\n\nroot = Example()\nroot.run()\n```\n![Hello World](screenshots/hello_world.png)\n\n## State \u0026 Data Binding\n```python\n# example/generic_textfield.py\nfrom PUI.PySide6 import *\n\ndata = State()\nclass Example(Application):\n    def __init__(self):\n        super().__init__()\n        data.var = 0\n\n    def content(self):\n        with Window(title=\"blah\"):\n            with VBox():\n                with HBox():\n                    Button(\"-\").click(self.on_minus)\n                    Label(f\"{data.var}\")\n                    Button(\"+\").click(self.on_plus)\n\n                TextField(data(\"var\")) # binding\n\n    def on_minus(self):\n        data.var -= 1\n\n    def on_plus(self):\n        data.var += 1\n\nroot = Example()\nroot.run()\n```\n![State \u0026 Data Binding](screenshots/pyside6_textfield.png)\n\n## View Component\n```python\n# example/bleak_list.py\n\n....\n\n@PUI # View Component\ndef DeviceView(device, advertising_data):\n    Label(f\"{device.address} {device.name} {advertising_data.rssi}\")\n\nclass GUI(Application):\n    def __init__(self, state):\n        super().__init__()\n        self.state = state\n\n    def content(self):\n        with Window(title=\"BLE List\"):\n            with VBox():\n                Label(f\"Found {len(self.state.scanned_devices)} devices\")\n                for device, advertising_data in self.state.scanned_devices:\n                    DeviceView(device, advertising_data)\n\n....\n```\n![View Component](screenshots/bleak_list.png)\n\n## Layout \u0026 Styling\n```python\n# example/pyside6_feedparser.py\n\n...\nwith VBox():\n    Label(title).qt(StyleSheet={\"font-weight\":\"bold\"}) # QT-specific\n\n    with HBox():\n        with Scroll():\n            with VBox():\n                for i,e in enumerate(entries):\n                    Label(e.title).click(self.entry_selected, i)\n                Spacer()\n\n        with Scroll().layout(weight=1): # Generic Layout Parameter\n            if 0 \u003c= selected and selected \u003c len(entries):\n                (Text(entries[selected].description)\n                    .layout(padding=10) # Generic Layout Parameter\n                    .qt(StyleSheet={\"background-color\":\"white\", \"color\":\"black\"})) # QT-specific\n...\n```\n![Layout \u0026 Styling](screenshots/feed_parser_padding.png)\n\n\n## Canvas\n```python\n# example/generic_canvas.py\n\nfrom PUI.PySide6 import *\n\ndata = State()\nclass Example(Application):\n    def __init__(self):\n        super().__init__()\n        data.var = 50\n\n    def content(self):\n        with Window(title=\"blah\", size=(640,480)):\n            with VBox():\n                Canvas(self.painter, data.var)\n                with HBox():\n                    Button(\"-\").click(self.on_minus)\n                    Label(f\"{data.var}\").layout(weight=1)\n                    Button(\"+\").click(self.on_plus)\n\n    @staticmethod\n    def painter(canvas, var):\n        canvas.drawText(var, var/2, f\"blah {var}\")\n        canvas.drawLine(var, var, var*2, var*3, color=0xFFFF00)\n\n    def on_minus(self):\n        data.var -= 1\n\n    def on_plus(self):\n        data.var += 1\n\nroot = Example()\nroot.run()\n```\n![Canvas](screenshots/pyside6_canvas.gif)\n\n## Cookbook\n`python -m cookbook PySide6` (requires pygments for syntax highlight)\n\n![Cookbook 1](screenshots/cookbook1.png)\n![Cookbook 2](screenshots/cookbook2.png)\n\n`python -m cookbook textual`\n![Cookbook textual](screenshots/cookbook_textual.png)\n\n`python -m cookbook flet`\n![Cookbook flet](screenshots/cookbook_flet.png)\n\n`python -m cookbook tkinter`\n![Cookbook tkinter](screenshots/cookbook_tkinter.png)\n\n# Hot Reload\n``` shell\npip install jurigged\n```\nThen PUI will take care of view update [(code)](https://github.com/buganini/PUI/blob/main/PUI/__init__.py#L11)\n\n\n# Backends\n## Tier-1\n* PySide6\n## Lower Priority\n* wx\n* tkinter\n    * or https://github.com/rdbende/Sun-Valley-ttk-theme\n* flet\n* textual (Text Mode)\n    * no canvas\n\n# Documents\n* [Components Reference](REFERENCE.md)\n* [Sizing Strategy](https://html-preview.github.io/?url=https://github.com/buganini/PUI/blob/main/doc/Sizing.html)\n\n# Used by\n* https://github.com/buganini/kikit-ui\n* https://github.com/solvcon/modmesh\n\n# TODO\n* Merge node and view\n* Dump with layout parameters and add test cases\n* [Toga](https://beeware.org/project/projects/libraries/toga/)\n* [ISSUE] textual layout sizing (cookbook scroll example)\n* [ISSUE] flet layout sizing (cookbook scroll example)\n* nested state trigger\n    * set state in PUIView __init__\n    * set state in setup() ?\n* Tabs(`tabposition`)\n* Lazy List\n* StateObject decorator\n* UI Flow\n    * Navigation Stack\n    * View Router\n    * Model Window/Dialog\n* Layout\n    * ZBox\n    * SwiftUI style overlay ??\n* Canvas\n    * Rect\n    * Arc\n    * Image\n    * ...\n* Table\n* Tree\n* Dialog\n* State with Pydantic support?","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuganini%2Fpui","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbuganini%2Fpui","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuganini%2Fpui/lists"}