qpuiq 0.10__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.
- qpuiq-0.10/LICENSE.txt +21 -0
- qpuiq-0.10/PKG-INFO +227 -0
- qpuiq-0.10/PUI/PySide6/__init__.py +49 -0
- qpuiq-0.10/PUI/PySide6/application.py +58 -0
- qpuiq-0.10/PUI/PySide6/base.py +222 -0
- qpuiq-0.10/PUI/PySide6/button.py +21 -0
- qpuiq-0.10/PUI/PySide6/canvas.py +288 -0
- qpuiq-0.10/PUI/PySide6/checkbox.py +32 -0
- qpuiq-0.10/PUI/PySide6/combobox.py +75 -0
- qpuiq-0.10/PUI/PySide6/dialog.py +72 -0
- qpuiq-0.10/PUI/PySide6/divider.py +23 -0
- qpuiq-0.10/PUI/PySide6/image.py +30 -0
- qpuiq-0.10/PUI/PySide6/label.py +33 -0
- qpuiq-0.10/PUI/PySide6/layout.py +72 -0
- qpuiq-0.10/PUI/PySide6/matplotlib.py +23 -0
- qpuiq-0.10/PUI/PySide6/mdi.py +33 -0
- qpuiq-0.10/PUI/PySide6/menu.py +85 -0
- qpuiq-0.10/PUI/PySide6/modal.py +132 -0
- qpuiq-0.10/PUI/PySide6/progressbar.py +17 -0
- qpuiq-0.10/PUI/PySide6/radiobutton.py +29 -0
- qpuiq-0.10/PUI/PySide6/scroll.py +153 -0
- qpuiq-0.10/PUI/PySide6/splitter.py +25 -0
- qpuiq-0.10/PUI/PySide6/tab.py +39 -0
- qpuiq-0.10/PUI/PySide6/table.py +89 -0
- qpuiq-0.10/PUI/PySide6/text.py +35 -0
- qpuiq-0.10/PUI/PySide6/textfield.py +62 -0
- qpuiq-0.10/PUI/PySide6/toolbar.py +57 -0
- qpuiq-0.10/PUI/PySide6/tree.py +120 -0
- qpuiq-0.10/PUI/PySide6/window.py +81 -0
- qpuiq-0.10/PUI/__init__.py +46 -0
- qpuiq-0.10/PUI/common.py +20 -0
- qpuiq-0.10/PUI/decorator.py +20 -0
- qpuiq-0.10/PUI/dom.py +238 -0
- qpuiq-0.10/PUI/flet/__init__.py +21 -0
- qpuiq-0.10/PUI/flet/application.py +42 -0
- qpuiq-0.10/PUI/flet/base.py +37 -0
- qpuiq-0.10/PUI/flet/button.py +20 -0
- qpuiq-0.10/PUI/flet/canvas.py +86 -0
- qpuiq-0.10/PUI/flet/checkbox.py +23 -0
- qpuiq-0.10/PUI/flet/label.py +27 -0
- qpuiq-0.10/PUI/flet/layout.py +50 -0
- qpuiq-0.10/PUI/flet/progressbar.py +21 -0
- qpuiq-0.10/PUI/flet/radiobutton.py +27 -0
- qpuiq-0.10/PUI/flet/scroll.py +83 -0
- qpuiq-0.10/PUI/flet/tab.py +42 -0
- qpuiq-0.10/PUI/flet/text.py +55 -0
- qpuiq-0.10/PUI/flet/textfield.py +58 -0
- qpuiq-0.10/PUI/flet/window.py +25 -0
- qpuiq-0.10/PUI/interfaces.py +97 -0
- qpuiq-0.10/PUI/node.py +407 -0
- qpuiq-0.10/PUI/state.py +698 -0
- qpuiq-0.10/PUI/textual/__init__.py +34 -0
- qpuiq-0.10/PUI/textual/application.py +82 -0
- qpuiq-0.10/PUI/textual/base.py +113 -0
- qpuiq-0.10/PUI/textual/button.py +17 -0
- qpuiq-0.10/PUI/textual/checkbox.py +21 -0
- qpuiq-0.10/PUI/textual/label.py +36 -0
- qpuiq-0.10/PUI/textual/layout.py +48 -0
- qpuiq-0.10/PUI/textual/progressbar.py +17 -0
- qpuiq-0.10/PUI/textual/radiobutton.py +24 -0
- qpuiq-0.10/PUI/textual/scroll.py +72 -0
- qpuiq-0.10/PUI/textual/tab.py +75 -0
- qpuiq-0.10/PUI/textual/text.py +32 -0
- qpuiq-0.10/PUI/textual/textfield.py +49 -0
- qpuiq-0.10/PUI/textual/window.py +7 -0
- qpuiq-0.10/PUI/timeline.py +36 -0
- qpuiq-0.10/PUI/tkinter/__init__.py +43 -0
- qpuiq-0.10/PUI/tkinter/application.py +49 -0
- qpuiq-0.10/PUI/tkinter/base.py +68 -0
- qpuiq-0.10/PUI/tkinter/button.py +15 -0
- qpuiq-0.10/PUI/tkinter/canvas.py +49 -0
- qpuiq-0.10/PUI/tkinter/checkbox.py +27 -0
- qpuiq-0.10/PUI/tkinter/label.py +17 -0
- qpuiq-0.10/PUI/tkinter/layout.py +114 -0
- qpuiq-0.10/PUI/tkinter/progressbar.py +17 -0
- qpuiq-0.10/PUI/tkinter/radiobutton.py +26 -0
- qpuiq-0.10/PUI/tkinter/scroll.py +201 -0
- qpuiq-0.10/PUI/tkinter/tab.py +52 -0
- qpuiq-0.10/PUI/tkinter/text.py +20 -0
- qpuiq-0.10/PUI/tkinter/textfield.py +53 -0
- qpuiq-0.10/PUI/tkinter/window.py +51 -0
- qpuiq-0.10/PUI/utils.py +15 -0
- qpuiq-0.10/PUI/view.py +161 -0
- qpuiq-0.10/PUI/wx/__init__.py +19 -0
- qpuiq-0.10/PUI/wx/application.py +44 -0
- qpuiq-0.10/PUI/wx/base.py +202 -0
- qpuiq-0.10/PUI/wx/button.py +16 -0
- qpuiq-0.10/PUI/wx/canvas.py +255 -0
- qpuiq-0.10/PUI/wx/checkbox.py +25 -0
- qpuiq-0.10/PUI/wx/combobox.py +72 -0
- qpuiq-0.10/PUI/wx/dialog.py +66 -0
- qpuiq-0.10/PUI/wx/divider.py +19 -0
- qpuiq-0.10/PUI/wx/label.py +18 -0
- qpuiq-0.10/PUI/wx/layout.py +46 -0
- qpuiq-0.10/PUI/wx/progressbar.py +17 -0
- qpuiq-0.10/PUI/wx/radiobutton.py +27 -0
- qpuiq-0.10/PUI/wx/scroll.py +44 -0
- qpuiq-0.10/PUI/wx/text.py +23 -0
- qpuiq-0.10/PUI/wx/textfield.py +56 -0
- qpuiq-0.10/PUI/wx/window.py +58 -0
- qpuiq-0.10/QPUIQ.egg-info/PKG-INFO +227 -0
- qpuiq-0.10/QPUIQ.egg-info/SOURCES.txt +109 -0
- qpuiq-0.10/QPUIQ.egg-info/dependency_links.txt +1 -0
- qpuiq-0.10/QPUIQ.egg-info/top_level.txt +1 -0
- qpuiq-0.10/README.md +217 -0
- qpuiq-0.10/setup.cfg +4 -0
- qpuiq-0.10/setup.py +17 -0
qpuiq-0.10/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 buganini@b612.tw
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qpuiq-0.10/PKG-INFO
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: qpuiq
|
|
3
|
+
Version: 0.10
|
|
4
|
+
Summary: "PUI" Python Declarative UI Framework
|
|
5
|
+
Home-page: https://github.com/buganini/PUI
|
|
6
|
+
Author: Buganini Chiu
|
|
7
|
+
Author-email: buganini@b612.tw
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE.txt
|
|
10
|
+
|
|
11
|
+
# What is PUI
|
|
12
|
+
PUI is a reactive/declarative UI framework with two-way data binding.
|
|
13
|
+
PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declarative flavor with virtual DOM and aims to maintain interoperability.
|
|
14
|
+
|
|
15
|
+
[Slides for SciWork 2023](https://speakerdeck.com/buganini/pui-declarative-ui-framework-for-python)
|
|
16
|
+
|
|
17
|
+
[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)
|
|
18
|
+
|
|
19
|
+
# Installation
|
|
20
|
+
```
|
|
21
|
+
pip install QPUIQ
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
# Get Started
|
|
25
|
+
## Hello World
|
|
26
|
+
```python
|
|
27
|
+
# example/hello_world.py
|
|
28
|
+
from PUI.PySide6 import *
|
|
29
|
+
|
|
30
|
+
@PUIApp
|
|
31
|
+
def Example():
|
|
32
|
+
with Window(title="test", size=(320,240)):
|
|
33
|
+
Label("Hello world")
|
|
34
|
+
|
|
35
|
+
root = Example()
|
|
36
|
+
root.run()
|
|
37
|
+
```
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
## State & Data Binding
|
|
41
|
+
```python
|
|
42
|
+
# example/generic_textfield.py
|
|
43
|
+
from PUI.PySide6 import *
|
|
44
|
+
|
|
45
|
+
data = State()
|
|
46
|
+
class Example(Application):
|
|
47
|
+
def __init__(self):
|
|
48
|
+
super().__init__()
|
|
49
|
+
data.var = 0
|
|
50
|
+
|
|
51
|
+
def content(self):
|
|
52
|
+
with Window(title="blah"):
|
|
53
|
+
with VBox():
|
|
54
|
+
with HBox():
|
|
55
|
+
Button("-").click(self.on_minus)
|
|
56
|
+
Label(f"{data.var}")
|
|
57
|
+
Button("+").click(self.on_plus)
|
|
58
|
+
|
|
59
|
+
TextField(data("var")) # binding
|
|
60
|
+
|
|
61
|
+
def on_minus(self):
|
|
62
|
+
data.var -= 1
|
|
63
|
+
|
|
64
|
+
def on_plus(self):
|
|
65
|
+
data.var += 1
|
|
66
|
+
|
|
67
|
+
root = Example()
|
|
68
|
+
root.run()
|
|
69
|
+
```
|
|
70
|
+

|
|
71
|
+
|
|
72
|
+
## View Component
|
|
73
|
+
```python
|
|
74
|
+
# example/bleak_list.py
|
|
75
|
+
|
|
76
|
+
....
|
|
77
|
+
|
|
78
|
+
@PUI # View Component
|
|
79
|
+
def DeviceView(device, advertising_data):
|
|
80
|
+
Label(f"{device.address} {device.name} {advertising_data.rssi}")
|
|
81
|
+
|
|
82
|
+
class GUI(Application):
|
|
83
|
+
def __init__(self, state):
|
|
84
|
+
super().__init__()
|
|
85
|
+
self.state = state
|
|
86
|
+
|
|
87
|
+
def content(self):
|
|
88
|
+
with Window(title="BLE List"):
|
|
89
|
+
with VBox():
|
|
90
|
+
Label(f"Found {len(self.state.scanned_devices)} devices")
|
|
91
|
+
for device, advertising_data in self.state.scanned_devices:
|
|
92
|
+
DeviceView(device, advertising_data)
|
|
93
|
+
|
|
94
|
+
....
|
|
95
|
+
```
|
|
96
|
+

|
|
97
|
+
|
|
98
|
+
## Layout & Styling
|
|
99
|
+
```python
|
|
100
|
+
# example/pyside6_feedparser.py
|
|
101
|
+
|
|
102
|
+
...
|
|
103
|
+
with VBox():
|
|
104
|
+
Label(title).qt(StyleSheet={"font-weight":"bold"}) # QT-specific
|
|
105
|
+
|
|
106
|
+
with HBox():
|
|
107
|
+
with Scroll():
|
|
108
|
+
with VBox():
|
|
109
|
+
for i,e in enumerate(entries):
|
|
110
|
+
Label(e.title).click(self.entry_selected, i)
|
|
111
|
+
Spacer()
|
|
112
|
+
|
|
113
|
+
with Scroll().layout(weight=1): # Generic Layout Parameter
|
|
114
|
+
if 0 <= selected and selected < len(entries):
|
|
115
|
+
(Text(entries[selected].description)
|
|
116
|
+
.layout(padding=10) # Generic Layout Parameter
|
|
117
|
+
.qt(StyleSheet={"background-color":"white", "color":"black"})) # QT-specific
|
|
118
|
+
...
|
|
119
|
+
```
|
|
120
|
+

|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
## Canvas
|
|
124
|
+
```python
|
|
125
|
+
# example/generic_canvas.py
|
|
126
|
+
|
|
127
|
+
from PUI.PySide6 import *
|
|
128
|
+
|
|
129
|
+
data = State()
|
|
130
|
+
class Example(Application):
|
|
131
|
+
def __init__(self):
|
|
132
|
+
super().__init__()
|
|
133
|
+
data.var = 50
|
|
134
|
+
|
|
135
|
+
def content(self):
|
|
136
|
+
with Window(title="blah", size=(640,480)):
|
|
137
|
+
with VBox():
|
|
138
|
+
Canvas(self.painter, data.var)
|
|
139
|
+
with HBox():
|
|
140
|
+
Button("-").click(self.on_minus)
|
|
141
|
+
Label(f"{data.var}").layout(weight=1)
|
|
142
|
+
Button("+").click(self.on_plus)
|
|
143
|
+
|
|
144
|
+
@staticmethod
|
|
145
|
+
def painter(canvas, var):
|
|
146
|
+
canvas.drawText(var, var/2, f"blah {var}")
|
|
147
|
+
canvas.drawLine(var, var, var*2, var*3, color=0xFFFF00)
|
|
148
|
+
|
|
149
|
+
def on_minus(self):
|
|
150
|
+
data.var -= 1
|
|
151
|
+
|
|
152
|
+
def on_plus(self):
|
|
153
|
+
data.var += 1
|
|
154
|
+
|
|
155
|
+
root = Example()
|
|
156
|
+
root.run()
|
|
157
|
+
```
|
|
158
|
+

|
|
159
|
+
|
|
160
|
+
## Cookbook
|
|
161
|
+
`python -m cookbook PySide6` (requires pygments for syntax highlight)
|
|
162
|
+
|
|
163
|
+

|
|
164
|
+

|
|
165
|
+
|
|
166
|
+
`python -m cookbook textual`
|
|
167
|
+

|
|
168
|
+
|
|
169
|
+
`python -m cookbook flet`
|
|
170
|
+

|
|
171
|
+
|
|
172
|
+
`python -m cookbook tkinter`
|
|
173
|
+

|
|
174
|
+
|
|
175
|
+
# Hot Reload
|
|
176
|
+
``` shell
|
|
177
|
+
pip install jurigged
|
|
178
|
+
```
|
|
179
|
+
Then PUI will take care of view update [(code)](https://github.com/buganini/PUI/blob/main/PUI/__init__.py#L11)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# Backends
|
|
183
|
+
## Tier-1
|
|
184
|
+
* PySide6
|
|
185
|
+
## Lower Priority
|
|
186
|
+
* wx
|
|
187
|
+
* tkinter
|
|
188
|
+
* or https://github.com/rdbende/Sun-Valley-ttk-theme
|
|
189
|
+
* flet
|
|
190
|
+
* textual (Text Mode)
|
|
191
|
+
* no canvas
|
|
192
|
+
|
|
193
|
+
# Components
|
|
194
|
+
[Reference](REFERENCE.md)
|
|
195
|
+
|
|
196
|
+
# Used by
|
|
197
|
+
* https://github.com/buganini/kikit-ui
|
|
198
|
+
* https://github.com/solvcon/modmesh
|
|
199
|
+
|
|
200
|
+
# TODO
|
|
201
|
+
* Merge node and view
|
|
202
|
+
* Dump with layout parameters and add test cases
|
|
203
|
+
* [Toga](https://beeware.org/project/projects/libraries/toga/)
|
|
204
|
+
* [ISSUE] textual layout sizing (cookbook scroll example)
|
|
205
|
+
* [ISSUE] flet layout sizing (cookbook scroll example)
|
|
206
|
+
* nested state trigger
|
|
207
|
+
* set state in PUIView __init__
|
|
208
|
+
* set state in setup() ?
|
|
209
|
+
* Tabs(`tabposition`)
|
|
210
|
+
* Lazy List
|
|
211
|
+
* StateObject decorator
|
|
212
|
+
* UI Flow
|
|
213
|
+
* Navigation Stack
|
|
214
|
+
* View Router
|
|
215
|
+
* Model Window/Dialog
|
|
216
|
+
* Layout
|
|
217
|
+
* ZBox
|
|
218
|
+
* SwiftUI style overlay ??
|
|
219
|
+
* Canvas
|
|
220
|
+
* Rect
|
|
221
|
+
* Arc
|
|
222
|
+
* Image
|
|
223
|
+
* ...
|
|
224
|
+
* Table
|
|
225
|
+
* Tree
|
|
226
|
+
* Dialog
|
|
227
|
+
* State with Pydantic support?
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QSizePolicy, QLayout
|
|
2
|
+
|
|
3
|
+
from .application import *
|
|
4
|
+
from .button import *
|
|
5
|
+
from .canvas import *
|
|
6
|
+
from .checkbox import *
|
|
7
|
+
from .combobox import *
|
|
8
|
+
from .dialog import *
|
|
9
|
+
from .divider import *
|
|
10
|
+
from .image import *
|
|
11
|
+
from .label import *
|
|
12
|
+
from .layout import *
|
|
13
|
+
from .modal import *
|
|
14
|
+
from .matplotlib import *
|
|
15
|
+
from .progressbar import *
|
|
16
|
+
from .radiobutton import *
|
|
17
|
+
from .scroll import *
|
|
18
|
+
from .splitter import *
|
|
19
|
+
from .table import *
|
|
20
|
+
from .tab import *
|
|
21
|
+
from .text import *
|
|
22
|
+
from .textfield import *
|
|
23
|
+
from .tree import *
|
|
24
|
+
from .window import *
|
|
25
|
+
from .mdi import *
|
|
26
|
+
from .toolbar import *
|
|
27
|
+
|
|
28
|
+
PUIView = QtPUIView
|
|
29
|
+
|
|
30
|
+
def PUI(func):
|
|
31
|
+
"""
|
|
32
|
+
PUI.PySide6.PUI triggers update() by signal/slot
|
|
33
|
+
"""
|
|
34
|
+
def func_wrapper(*args, **kwargs):
|
|
35
|
+
class PUIViewWrapper(QtPUIView):
|
|
36
|
+
pui_virtual = True
|
|
37
|
+
def __init__(self, name):
|
|
38
|
+
self.name = name
|
|
39
|
+
super().__init__()
|
|
40
|
+
|
|
41
|
+
def content(self):
|
|
42
|
+
return func(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
ret = PUIViewWrapper(func.__name__)
|
|
45
|
+
return ret
|
|
46
|
+
|
|
47
|
+
return func_wrapper
|
|
48
|
+
|
|
49
|
+
PUI_BACKEND = "PySide6"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from .base import *
|
|
3
|
+
|
|
4
|
+
class QtApplicationSignal(QtCore.QObject):
|
|
5
|
+
quit = QtCore.Signal()
|
|
6
|
+
|
|
7
|
+
class Application(QtPUIView):
|
|
8
|
+
def __init__(self, icon=None):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.ui = None
|
|
11
|
+
self.icon = icon
|
|
12
|
+
self._qtappsignal = QtApplicationSignal()
|
|
13
|
+
self._qtappsignal.quit.connect(self._quit, QtCore.Qt.ConnectionType.QueuedConnection) # Use QueuedConnection to prevent nested trigger
|
|
14
|
+
|
|
15
|
+
def redraw(self):
|
|
16
|
+
if self.ui:
|
|
17
|
+
super().redraw()
|
|
18
|
+
else:
|
|
19
|
+
self.sync()
|
|
20
|
+
|
|
21
|
+
def update(self, prev=None):
|
|
22
|
+
if not self.ui:
|
|
23
|
+
from PySide6 import QtWidgets
|
|
24
|
+
self.ui = QtWidgets.QApplication([])
|
|
25
|
+
if self.icon:
|
|
26
|
+
self.ui.setWindowIcon(QtGui.QIcon(self.icon))
|
|
27
|
+
|
|
28
|
+
super().update(prev)
|
|
29
|
+
|
|
30
|
+
def addChild(self, idx, child):
|
|
31
|
+
child.outer.show()
|
|
32
|
+
|
|
33
|
+
def removeChild(self, idx, child):
|
|
34
|
+
child.outer.close()
|
|
35
|
+
|
|
36
|
+
def start(self):
|
|
37
|
+
self.ui.exec()
|
|
38
|
+
|
|
39
|
+
def quit(self):
|
|
40
|
+
self._qtappsignal.quit.emit()
|
|
41
|
+
|
|
42
|
+
def _quit(self):
|
|
43
|
+
self.ui.quit()
|
|
44
|
+
|
|
45
|
+
def PUIApp(func):
|
|
46
|
+
def func_wrapper(*args, **kwargs):
|
|
47
|
+
class PUIAppWrapper(Application):
|
|
48
|
+
def __init__(self, name):
|
|
49
|
+
self.name = name
|
|
50
|
+
super().__init__()
|
|
51
|
+
|
|
52
|
+
def content(self):
|
|
53
|
+
return func(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
ret = PUIAppWrapper(func.__name__)
|
|
56
|
+
return ret
|
|
57
|
+
|
|
58
|
+
return func_wrapper
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from PySide6 import QtCore, QtWidgets, QtGui
|
|
3
|
+
|
|
4
|
+
class QtViewSignal(QtCore.QObject):
|
|
5
|
+
redraw = QtCore.Signal()
|
|
6
|
+
|
|
7
|
+
def _apply_params(ui, node):
|
|
8
|
+
styles = {}
|
|
9
|
+
if node.style_fontsize:
|
|
10
|
+
styles["font"] = f"{node.style_fontsize}pt"
|
|
11
|
+
if node.style_fontfamily:
|
|
12
|
+
styles["font-family"] = node.style_fontfamily
|
|
13
|
+
if node.style_color:
|
|
14
|
+
styles["color"] = f"#{node.style_color:06X}"
|
|
15
|
+
if node.style_bgcolor:
|
|
16
|
+
styles["background-color"] = f"#{node.style_bgcolor:06X}"
|
|
17
|
+
|
|
18
|
+
style = node.qt_params.get("Style")
|
|
19
|
+
if not style is None:
|
|
20
|
+
ui.setStyle(style)
|
|
21
|
+
|
|
22
|
+
HorizontalPolicy = node.qt_params.get("HorizontalPolicy")
|
|
23
|
+
if not HorizontalPolicy is None:
|
|
24
|
+
ui.sizePolicy().setHorizontalPolicy(HorizontalPolicy)
|
|
25
|
+
|
|
26
|
+
VerticalPolicy = node.qt_params.get("VerticalPolicy")
|
|
27
|
+
if not VerticalPolicy is None:
|
|
28
|
+
ui.sizePolicy().setVerticalPolicy(VerticalPolicy)
|
|
29
|
+
|
|
30
|
+
SizeConstraint = node.qt_params.get("SizeConstraint")
|
|
31
|
+
if not SizeConstraint is None:
|
|
32
|
+
ui.setSizeConstraint(SizeConstraint)
|
|
33
|
+
|
|
34
|
+
StyleSheet = node.qt_params.get("StyleSheet", {})
|
|
35
|
+
for k,v in StyleSheet.items():
|
|
36
|
+
styles[k] = v
|
|
37
|
+
|
|
38
|
+
if hasattr(ui, "setStyleSheet"):
|
|
39
|
+
ui.setStyleSheet("".join([f"{k}:{v};" for k,v in styles.items()]))
|
|
40
|
+
|
|
41
|
+
if node.layout_padding:
|
|
42
|
+
ui.setContentsMargins(*trbl2ltrb(node.layout_padding))
|
|
43
|
+
|
|
44
|
+
class QtPUIView(PUIView):
|
|
45
|
+
pui_virtual = True
|
|
46
|
+
def __init__(self):
|
|
47
|
+
super().__init__()
|
|
48
|
+
self.qt_params = {}
|
|
49
|
+
self._qtsignal = QtViewSignal()
|
|
50
|
+
self._qtsignal.redraw.connect(self.sync, QtCore.Qt.ConnectionType.QueuedConnection) # Use QueuedConnection to prevent nested trigger
|
|
51
|
+
|
|
52
|
+
def destroy(self, direct):
|
|
53
|
+
if direct:
|
|
54
|
+
if self.ui: # PUIView doesn't have ui
|
|
55
|
+
self.ui.deleteLater()
|
|
56
|
+
self.ui = None
|
|
57
|
+
super().destroy(direct)
|
|
58
|
+
|
|
59
|
+
def redraw(self):
|
|
60
|
+
self.dirty = True
|
|
61
|
+
if self.updating:
|
|
62
|
+
return
|
|
63
|
+
self.updating = True
|
|
64
|
+
self._qtsignal.redraw.emit()
|
|
65
|
+
|
|
66
|
+
def update(self, prev=None):
|
|
67
|
+
super().update(prev)
|
|
68
|
+
_apply_params(self.ui, self)
|
|
69
|
+
|
|
70
|
+
def qt(self, **kwargs):
|
|
71
|
+
for k,v in kwargs.items():
|
|
72
|
+
self.qt_params[k] = v
|
|
73
|
+
return self
|
|
74
|
+
|
|
75
|
+
class QtBaseWidget(PUINode):
|
|
76
|
+
pui_terminal = True
|
|
77
|
+
|
|
78
|
+
def __init__(self):
|
|
79
|
+
super().__init__()
|
|
80
|
+
self.qt_params = {}
|
|
81
|
+
|
|
82
|
+
def destroy(self, direct):
|
|
83
|
+
if direct:
|
|
84
|
+
if self.ui:
|
|
85
|
+
self.ui.deleteLater()
|
|
86
|
+
self.ui = None
|
|
87
|
+
super().destroy(direct)
|
|
88
|
+
|
|
89
|
+
def update(self, prev=None):
|
|
90
|
+
super().update(prev)
|
|
91
|
+
|
|
92
|
+
sizePolicy = self.ui.sizePolicy()
|
|
93
|
+
if self.layout_width is not None:
|
|
94
|
+
sizePolicy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred)
|
|
95
|
+
if self.layout_height is not None:
|
|
96
|
+
sizePolicy.setVerticalPolicy(QtWidgets.QSizePolicy.Preferred)
|
|
97
|
+
self.ui.setSizePolicy(sizePolicy)
|
|
98
|
+
|
|
99
|
+
if not hasattr(self.ui, "origSizeHint"):
|
|
100
|
+
self.ui.origSizeHint = self.ui.sizeHint
|
|
101
|
+
self.ui.sizeHint = self.qtSizeHint
|
|
102
|
+
|
|
103
|
+
_apply_params(self.ui, self)
|
|
104
|
+
|
|
105
|
+
def qtSizeHint(self):
|
|
106
|
+
node = self.get_node()
|
|
107
|
+
if not node.ui:
|
|
108
|
+
return QtCore.QSize(0, 0)
|
|
109
|
+
sh = node.ui.origSizeHint()
|
|
110
|
+
w = sh.width()
|
|
111
|
+
h = sh.height()
|
|
112
|
+
if not node.layout_width is None:
|
|
113
|
+
w = node.layout_width
|
|
114
|
+
if not node.layout_height is None:
|
|
115
|
+
h = node.layout_height
|
|
116
|
+
return QtCore.QSize(w, h)
|
|
117
|
+
|
|
118
|
+
def qt(self, **kwargs):
|
|
119
|
+
for k,v in kwargs.items():
|
|
120
|
+
self.qt_params[k] = v
|
|
121
|
+
return self
|
|
122
|
+
|
|
123
|
+
class QtBaseLayout(PUINode):
|
|
124
|
+
def __init__(self):
|
|
125
|
+
super().__init__()
|
|
126
|
+
self.qt_params = {}
|
|
127
|
+
if not isinstance(self.non_virtual_parent, QtBaseLayout):
|
|
128
|
+
self.layout_padding = (11,11,11,11)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def outer(self):
|
|
132
|
+
return self.ui
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def inner(self):
|
|
136
|
+
return self.layout
|
|
137
|
+
|
|
138
|
+
def destroy(self, direct):
|
|
139
|
+
if direct:
|
|
140
|
+
if self.ui:
|
|
141
|
+
self.ui.deleteLater()
|
|
142
|
+
self.layout = None
|
|
143
|
+
self.ui = None
|
|
144
|
+
super().destroy(direct)
|
|
145
|
+
|
|
146
|
+
def update(self, prev=None):
|
|
147
|
+
super().update(prev)
|
|
148
|
+
_apply_params(self.ui, self)
|
|
149
|
+
|
|
150
|
+
def addChild(self, idx, child):
|
|
151
|
+
from .modal import Modal
|
|
152
|
+
from .layout import Spacer
|
|
153
|
+
if isinstance(child, Spacer):
|
|
154
|
+
self.layout.insertItem(idx, child.outer)
|
|
155
|
+
elif isinstance(child, Modal):
|
|
156
|
+
pass
|
|
157
|
+
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
158
|
+
params = {}
|
|
159
|
+
if not child.layout_weight is None:
|
|
160
|
+
params["stretch"] = child.layout_weight
|
|
161
|
+
self.layout.insertWidget(idx, child.outer, **params)
|
|
162
|
+
|
|
163
|
+
def removeChild(self, idx, child):
|
|
164
|
+
from .modal import Modal
|
|
165
|
+
from .layout import Spacer
|
|
166
|
+
if isinstance(child, Spacer):
|
|
167
|
+
self.layout.removeItem(child.outer)
|
|
168
|
+
elif isinstance(child, Modal):
|
|
169
|
+
pass
|
|
170
|
+
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
171
|
+
child.outer.setParent(None)
|
|
172
|
+
|
|
173
|
+
def qt(self, **kwargs):
|
|
174
|
+
for k,v in kwargs.items():
|
|
175
|
+
self.qt_params[k] = v
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
class QtBaseFrame(QtBaseWidget):
|
|
179
|
+
pui_terminal = False
|
|
180
|
+
|
|
181
|
+
def destroy(self, direct):
|
|
182
|
+
if direct:
|
|
183
|
+
if self.ui:
|
|
184
|
+
self.ui.deleteLater()
|
|
185
|
+
self.ui = None
|
|
186
|
+
|
|
187
|
+
def addChild(self, idx, child):
|
|
188
|
+
if idx != 0:
|
|
189
|
+
return
|
|
190
|
+
if isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
191
|
+
self.ui.setWidget(child.outer)
|
|
192
|
+
elif child.children:
|
|
193
|
+
self.addChild(idx, child.children[0])
|
|
194
|
+
|
|
195
|
+
def removeChild(self, idx, child):
|
|
196
|
+
if idx != 0:
|
|
197
|
+
return
|
|
198
|
+
if isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
199
|
+
child.outer.setParent(None)
|
|
200
|
+
elif child.children:
|
|
201
|
+
self.removeChild(idx, child.children[0])
|
|
202
|
+
|
|
203
|
+
class QtInPui(QtBaseWidget):
|
|
204
|
+
def __init__(self, widget, *args):
|
|
205
|
+
self._internal_tag = str(id(widget))
|
|
206
|
+
super().__init__(*args)
|
|
207
|
+
self.ui = widget
|
|
208
|
+
|
|
209
|
+
def destroy(self, direct):
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
class PuiInQt(QtPUIView):
|
|
213
|
+
def __init__(self, ui):
|
|
214
|
+
super().__init__()
|
|
215
|
+
self.ui = ui
|
|
216
|
+
self.ui.update()
|
|
217
|
+
|
|
218
|
+
def addChild(self, idx, child):
|
|
219
|
+
self.ui.addChild(idx, child)
|
|
220
|
+
|
|
221
|
+
def removeChild(self, idx, child):
|
|
222
|
+
self.ui.removeChild(idx, child)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from .base import *
|
|
3
|
+
|
|
4
|
+
class Button(QtBaseWidget):
|
|
5
|
+
def __init__(self, text):
|
|
6
|
+
super().__init__()
|
|
7
|
+
self.text = text
|
|
8
|
+
|
|
9
|
+
def update(self, prev):
|
|
10
|
+
if prev and prev.ui:
|
|
11
|
+
self.ui = prev.ui
|
|
12
|
+
self.ui.setText(self.text)
|
|
13
|
+
try:
|
|
14
|
+
self.ui.clicked.disconnect()
|
|
15
|
+
except:
|
|
16
|
+
pass
|
|
17
|
+
prev.callback = None
|
|
18
|
+
else:
|
|
19
|
+
self.ui = QtWidgets.QPushButton(text=self.text)
|
|
20
|
+
self.ui.clicked.connect(self._clicked)
|
|
21
|
+
super().update(prev)
|