qpuiq 0.10__tar.gz → 0.24__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 → qpuiq-0.24}/PKG-INFO +21 -14
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/base.py +76 -13
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/canvas.py +77 -20
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/combobox.py +13 -3
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/divider.py +2 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/image.py +20 -2
- qpuiq-0.24/PUI/PySide6/layout.py +141 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/scroll.py +3 -1
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/table.py +71 -13
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/textfield.py +4 -4
- qpuiq-0.24/PUI/PySide6/tree.py +290 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/window.py +4 -3
- {qpuiq-0.10 → qpuiq-0.24}/PUI/__init__.py +1 -1
- {qpuiq-0.10 → qpuiq-0.24}/PUI/common.py +6 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/dom.py +37 -12
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/__init__.py +1 -0
- qpuiq-0.24/PUI/flet/divider.py +14 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/textfield.py +2 -2
- {qpuiq-0.10 → qpuiq-0.24}/PUI/node.py +25 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/state.py +18 -5
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/__init__.py +1 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/application.py +2 -2
- qpuiq-0.24/PUI/textual/base.py +148 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/label.py +2 -2
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/layout.py +4 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/scroll.py +6 -4
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/textfield.py +10 -4
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/canvas.py +3 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/textfield.py +2 -2
- qpuiq-0.24/PUI/wx/base.py +246 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/canvas.py +2 -2
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/combobox.py +14 -5
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/layout.py +6 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/progressbar.py +2 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/scroll.py +13 -2
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/textfield.py +15 -5
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/window.py +6 -0
- {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/PKG-INFO +21 -14
- {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/SOURCES.txt +1 -0
- {qpuiq-0.10 → qpuiq-0.24}/README.md +20 -13
- qpuiq-0.10/PUI/PySide6/layout.py +0 -72
- qpuiq-0.10/PUI/PySide6/tree.py +0 -120
- qpuiq-0.10/PUI/textual/base.py +0 -113
- qpuiq-0.10/PUI/wx/base.py +0 -202
- {qpuiq-0.10 → qpuiq-0.24}/LICENSE.txt +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/__init__.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/application.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/button.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/checkbox.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/dialog.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/label.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/matplotlib.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/mdi.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/menu.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/modal.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/progressbar.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/radiobutton.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/splitter.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/tab.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/text.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/toolbar.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/decorator.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/application.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/base.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/button.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/canvas.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/checkbox.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/label.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/layout.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/progressbar.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/radiobutton.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/scroll.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/tab.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/text.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/window.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/interfaces.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/button.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/checkbox.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/progressbar.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/radiobutton.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/tab.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/text.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/window.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/timeline.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/__init__.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/application.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/base.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/button.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/checkbox.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/label.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/layout.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/progressbar.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/radiobutton.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/scroll.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/tab.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/text.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/window.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/utils.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/view.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/__init__.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/application.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/button.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/checkbox.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/dialog.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/divider.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/label.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/radiobutton.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/text.py +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/dependency_links.txt +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/top_level.txt +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/setup.cfg +0 -0
- {qpuiq-0.10 → qpuiq-0.24}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: qpuiq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.24
|
|
4
4
|
Summary: "PUI" Python Declarative UI Framework
|
|
5
5
|
Home-page: https://github.com/buganini/PUI
|
|
6
6
|
Author: Buganini Chiu
|
|
@@ -17,8 +17,23 @@ PUI doesn't do UI itself, it turns imperative UI libraries into reactive/declara
|
|
|
17
17
|
[CPPUI: Experimental C++ Version](https://github.com/buganini/CPPUI)
|
|
18
18
|
|
|
19
19
|
# Installation
|
|
20
|
-
```
|
|
20
|
+
``` shell
|
|
21
21
|
pip install QPUIQ
|
|
22
|
+
|
|
23
|
+
# Optional, for hot-reload
|
|
24
|
+
pip install jurigged
|
|
25
|
+
|
|
26
|
+
# Optional, for PySide6 backend
|
|
27
|
+
pip install PySide6
|
|
28
|
+
|
|
29
|
+
# Optional, for textual backend
|
|
30
|
+
pip install textual
|
|
31
|
+
|
|
32
|
+
# Optional, for wxPython backend
|
|
33
|
+
pip install wxPython
|
|
34
|
+
|
|
35
|
+
# Optional, for flet backend
|
|
36
|
+
pip install flet
|
|
22
37
|
```
|
|
23
38
|
|
|
24
39
|
# Get Started
|
|
@@ -190,38 +205,30 @@ Then PUI will take care of view update [(code)](https://github.com/buganini/PUI/
|
|
|
190
205
|
* textual (Text Mode)
|
|
191
206
|
* no canvas
|
|
192
207
|
|
|
193
|
-
#
|
|
194
|
-
[Reference](
|
|
208
|
+
# Documents
|
|
209
|
+
* [Components Reference](https://buganini.github.io/PUI/doc/Reference.html)
|
|
210
|
+
* [Layout Strategy](https://buganini.github.io/PUI/doc/Layout.html)
|
|
195
211
|
|
|
196
212
|
# Used by
|
|
197
|
-
* https://github.com/buganini/
|
|
213
|
+
* https://github.com/buganini/Kikakuka
|
|
198
214
|
* https://github.com/solvcon/modmesh
|
|
199
215
|
|
|
200
216
|
# TODO
|
|
201
|
-
* Merge node and view
|
|
202
217
|
* Dump with layout parameters and add test cases
|
|
203
218
|
* [Toga](https://beeware.org/project/projects/libraries/toga/)
|
|
204
|
-
* [ISSUE] textual layout sizing (cookbook scroll example)
|
|
205
219
|
* [ISSUE] flet layout sizing (cookbook scroll example)
|
|
206
220
|
* nested state trigger
|
|
207
221
|
* set state in PUIView __init__
|
|
208
222
|
* set state in setup() ?
|
|
209
223
|
* Tabs(`tabposition`)
|
|
210
224
|
* Lazy List
|
|
211
|
-
* StateObject decorator
|
|
212
225
|
* UI Flow
|
|
213
226
|
* Navigation Stack
|
|
214
227
|
* View Router
|
|
215
228
|
* Model Window/Dialog
|
|
216
229
|
* Layout
|
|
217
|
-
* ZBox
|
|
218
230
|
* SwiftUI style overlay ??
|
|
219
231
|
* Canvas
|
|
220
|
-
* Rect
|
|
221
232
|
* Arc
|
|
222
|
-
* Image
|
|
223
233
|
* ...
|
|
224
|
-
* Table
|
|
225
|
-
* Tree
|
|
226
|
-
* Dialog
|
|
227
234
|
* State with Pydantic support?
|
|
@@ -72,6 +72,19 @@ class QtPUIView(PUIView):
|
|
|
72
72
|
self.qt_params[k] = v
|
|
73
73
|
return self
|
|
74
74
|
|
|
75
|
+
class EventFilter(QtCore.QObject):
|
|
76
|
+
def __init__(self):
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.node = None
|
|
79
|
+
|
|
80
|
+
def eventFilter(self, obj, event):
|
|
81
|
+
node = self.node.get_node()
|
|
82
|
+
if event.type() == QtCore.QEvent.DragEnter:
|
|
83
|
+
return node.handleDragEnterEvent(event)
|
|
84
|
+
elif event.type() == QtCore.QEvent.Drop:
|
|
85
|
+
return node.handleDropEvent(event)
|
|
86
|
+
return super().eventFilter(obj, event)
|
|
87
|
+
|
|
75
88
|
class QtBaseWidget(PUINode):
|
|
76
89
|
pui_terminal = True
|
|
77
90
|
|
|
@@ -89,6 +102,12 @@ class QtBaseWidget(PUINode):
|
|
|
89
102
|
def update(self, prev=None):
|
|
90
103
|
super().update(prev)
|
|
91
104
|
|
|
105
|
+
if prev:
|
|
106
|
+
self.eventFilter = prev.eventFilter
|
|
107
|
+
else:
|
|
108
|
+
self.eventFilter = EventFilter()
|
|
109
|
+
self.eventFilter.node = self
|
|
110
|
+
|
|
92
111
|
sizePolicy = self.ui.sizePolicy()
|
|
93
112
|
if self.layout_width is not None:
|
|
94
113
|
sizePolicy.setHorizontalPolicy(QtWidgets.QSizePolicy.Preferred)
|
|
@@ -102,6 +121,29 @@ class QtBaseWidget(PUINode):
|
|
|
102
121
|
|
|
103
122
|
_apply_params(self.ui, self)
|
|
104
123
|
|
|
124
|
+
def postUpdate(self):
|
|
125
|
+
if self.ui:
|
|
126
|
+
if self._onDropped:
|
|
127
|
+
self.ui.setAcceptDrops(True)
|
|
128
|
+
self.ui.installEventFilter(self.eventFilter)
|
|
129
|
+
else:
|
|
130
|
+
self.ui.setAcceptDrops(False)
|
|
131
|
+
super().postUpdate()
|
|
132
|
+
|
|
133
|
+
def handleDragEnterEvent(self, event):
|
|
134
|
+
if self._onDragEntered:
|
|
135
|
+
return self._onDragEntered[0](event, *self._onDragEntered[1], **self._onDragEntered[2])
|
|
136
|
+
else:
|
|
137
|
+
event.ignore()
|
|
138
|
+
return True
|
|
139
|
+
|
|
140
|
+
def handleDropEvent(self, event):
|
|
141
|
+
if self._onDropped:
|
|
142
|
+
return self._onDropped[0](event, *self._onDropped[1], **self._onDropped[2])
|
|
143
|
+
else:
|
|
144
|
+
event.ignore()
|
|
145
|
+
return False
|
|
146
|
+
|
|
105
147
|
def qtSizeHint(self):
|
|
106
148
|
node = self.get_node()
|
|
107
149
|
if not node.ui:
|
|
@@ -120,7 +162,11 @@ class QtBaseWidget(PUINode):
|
|
|
120
162
|
self.qt_params[k] = v
|
|
121
163
|
return self
|
|
122
164
|
|
|
123
|
-
class QtBaseLayout(
|
|
165
|
+
class QtBaseLayout(QtBaseWidget):
|
|
166
|
+
pui_terminal = False
|
|
167
|
+
container_x = False
|
|
168
|
+
container_y = False
|
|
169
|
+
|
|
124
170
|
def __init__(self):
|
|
125
171
|
super().__init__()
|
|
126
172
|
self.qt_params = {}
|
|
@@ -136,44 +182,61 @@ class QtBaseLayout(PUINode):
|
|
|
136
182
|
return self.layout
|
|
137
183
|
|
|
138
184
|
def destroy(self, direct):
|
|
139
|
-
if direct:
|
|
140
|
-
if self.ui:
|
|
141
|
-
self.ui.deleteLater()
|
|
142
185
|
self.layout = None
|
|
143
|
-
self.ui = None
|
|
144
186
|
super().destroy(direct)
|
|
145
187
|
|
|
146
188
|
def update(self, prev=None):
|
|
189
|
+
if prev and prev.ui:
|
|
190
|
+
self.mounted_children = prev.mounted_children
|
|
191
|
+
else:
|
|
192
|
+
self.mounted_children = []
|
|
193
|
+
|
|
147
194
|
super().update(prev)
|
|
148
|
-
_apply_params(self.ui, self)
|
|
149
195
|
|
|
150
196
|
def addChild(self, idx, child):
|
|
151
197
|
from .modal import Modal
|
|
152
198
|
from .layout import Spacer
|
|
153
199
|
if isinstance(child, Spacer):
|
|
154
|
-
self.
|
|
200
|
+
self.qtlayout.insertItem(idx, child.outer)
|
|
201
|
+
self.mounted_children.insert(idx, child)
|
|
155
202
|
elif isinstance(child, Modal):
|
|
156
203
|
pass
|
|
157
204
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
158
205
|
params = {}
|
|
159
206
|
if not child.layout_weight is None:
|
|
160
207
|
params["stretch"] = child.layout_weight
|
|
161
|
-
self.
|
|
208
|
+
self.qtlayout.insertWidget(idx, child.outer, **params)
|
|
209
|
+
self.mounted_children.insert(idx, child)
|
|
162
210
|
|
|
163
211
|
def removeChild(self, idx, child):
|
|
164
212
|
from .modal import Modal
|
|
165
213
|
from .layout import Spacer
|
|
166
214
|
if isinstance(child, Spacer):
|
|
167
|
-
self.
|
|
215
|
+
self.qtlayout.removeItem(child.outer)
|
|
216
|
+
self.mounted_children.pop(idx)
|
|
168
217
|
elif isinstance(child, Modal):
|
|
169
218
|
pass
|
|
170
219
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
171
220
|
child.outer.setParent(None)
|
|
221
|
+
self.mounted_children.pop(idx)
|
|
172
222
|
|
|
173
|
-
def
|
|
174
|
-
|
|
175
|
-
self.
|
|
176
|
-
|
|
223
|
+
def postUpdate(self):
|
|
224
|
+
if self.ui:
|
|
225
|
+
if self._onDropped:
|
|
226
|
+
self.ui.setAcceptDrops(True)
|
|
227
|
+
self.ui.installEventFilter(self.eventFilter)
|
|
228
|
+
else:
|
|
229
|
+
self.ui.setAcceptDrops(False)
|
|
230
|
+
|
|
231
|
+
super().postUpdate()
|
|
232
|
+
|
|
233
|
+
if self.container_x or self.container_y:
|
|
234
|
+
for i, child in enumerate(self.mounted_children):
|
|
235
|
+
child = child.get_node()
|
|
236
|
+
self.mounted_children[i] = child.get_node()
|
|
237
|
+
|
|
238
|
+
weight = child.layout_weight
|
|
239
|
+
self.qtlayout.setStretch(i, weight if weight else 0)
|
|
177
240
|
|
|
178
241
|
class QtBaseFrame(QtBaseWidget):
|
|
179
242
|
pui_terminal = False
|
|
@@ -2,12 +2,11 @@ from .. import *
|
|
|
2
2
|
from .base import *
|
|
3
3
|
|
|
4
4
|
from PySide6 import QtWidgets, QtGui
|
|
5
|
-
from PySide6.QtGui import QPainter, QColor, QPainterPath
|
|
6
|
-
from PySide6.QtCore import QPoint
|
|
5
|
+
from PySide6.QtGui import QPainter, QColor, QPainterPath, QImage
|
|
7
6
|
|
|
8
7
|
class PUIQtCanvas(QtWidgets.QWidget):
|
|
9
8
|
def __init__(self, node, width=None, height=None):
|
|
10
|
-
self.
|
|
9
|
+
self.puinode = node
|
|
11
10
|
self.width = width
|
|
12
11
|
self.height = height
|
|
13
12
|
super().__init__()
|
|
@@ -19,25 +18,25 @@ class PUIQtCanvas(QtWidgets.QWidget):
|
|
|
19
18
|
e = PUIEvent()
|
|
20
19
|
e.button = event.button().value
|
|
21
20
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
22
|
-
self.
|
|
21
|
+
self.puinode._dblclicked(e)
|
|
23
22
|
|
|
24
23
|
def mousePressEvent(self, event):
|
|
25
24
|
e = PUIEvent()
|
|
26
25
|
e.button = event.button().value
|
|
27
26
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
28
|
-
self.
|
|
27
|
+
self.puinode._mousedown(e)
|
|
29
28
|
|
|
30
29
|
def mouseReleaseEvent(self, event):
|
|
31
30
|
e = PUIEvent()
|
|
32
31
|
e.button = event.button().value
|
|
33
32
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
34
|
-
self.
|
|
33
|
+
self.puinode._mouseup(e)
|
|
35
34
|
|
|
36
35
|
def mouseMoveEvent(self, event):
|
|
37
36
|
e = PUIEvent()
|
|
38
37
|
e.button = event.button().value
|
|
39
38
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
40
|
-
self.
|
|
39
|
+
self.puinode._mousemove(e)
|
|
41
40
|
|
|
42
41
|
def wheelEvent(self, event):
|
|
43
42
|
e = PUIEvent()
|
|
@@ -46,25 +45,59 @@ class PUIQtCanvas(QtWidgets.QWidget):
|
|
|
46
45
|
e.x_delta = event.pixelDelta().x()
|
|
47
46
|
e.v_delta = event.angleDelta().y()
|
|
48
47
|
e.h_delta = event.angleDelta().x()
|
|
49
|
-
|
|
48
|
+
modifier = 0
|
|
49
|
+
emodifiers = event.modifiers()
|
|
50
|
+
if emodifiers & QtCore.Qt.ShiftModifier:
|
|
51
|
+
modifier |= KeyModifier.SHIFT
|
|
52
|
+
if emodifiers & QtCore.Qt.ControlModifier:
|
|
53
|
+
modifier |= KeyModifier.CTRL
|
|
54
|
+
if emodifiers & QtCore.Qt.AltModifier:
|
|
55
|
+
modifier |= KeyModifier.ALT
|
|
56
|
+
if emodifiers & QtCore.Qt.MetaModifier:
|
|
57
|
+
modifier |= KeyModifier.META
|
|
58
|
+
e.modifiers = modifier
|
|
59
|
+
self.puinode._wheel(e)
|
|
50
60
|
|
|
51
61
|
def paintEvent(self, event):
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
puinode = self.puinode.get_node()
|
|
63
|
+
puinode.qpainter = QPainter()
|
|
64
|
+
puinode.qpainter.begin(self)
|
|
65
|
+
puinode.qpainter.setRenderHints(QtGui.QPainter.Antialiasing, True)
|
|
56
66
|
|
|
57
|
-
if not
|
|
67
|
+
if not puinode.style_bgcolor is None:
|
|
58
68
|
bgBrush = QtGui.QBrush()
|
|
59
|
-
bgBrush.setColor(QtGui.QColor(
|
|
69
|
+
bgBrush.setColor(QtGui.QColor(puinode.style_bgcolor))
|
|
60
70
|
bgBrush.setStyle(QtCore.Qt.SolidPattern)
|
|
61
71
|
rect = QtCore.QRect(0, 0, self.width or self.geometry().width(), self.height or self.geometry().height())
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
puinode.qpainter.fillRect(rect, bgBrush)
|
|
73
|
+
|
|
74
|
+
puinode.width = self.geometry().width()
|
|
75
|
+
puinode.height = self.geometry().height()
|
|
76
|
+
immediate = puinode.painter(puinode, *puinode.args)
|
|
77
|
+
puinode.qpainter.end()
|
|
78
|
+
puinode.qpainter = None
|
|
79
|
+
if immediate:
|
|
80
|
+
self.update()
|
|
81
|
+
|
|
82
|
+
class ImageResource():
|
|
83
|
+
@staticmethod
|
|
84
|
+
def load(path):
|
|
85
|
+
ir = ImageResource()
|
|
86
|
+
ir.qimage = QImage(path)
|
|
87
|
+
return ir
|
|
88
|
+
|
|
89
|
+
def crop(self, x, y, width, height):
|
|
90
|
+
ir = ImageResource()
|
|
91
|
+
ir.qimage = self.qimage.copy(x, y, width, height)
|
|
92
|
+
return ir
|
|
93
|
+
|
|
94
|
+
def scale(self, width, height, keepAspectRatio=True, quality=0):
|
|
95
|
+
ir = ImageResource()
|
|
96
|
+
method = {
|
|
97
|
+
0: QtCore.Qt.TransformationMode.FastTransformation
|
|
98
|
+
}.get(quality, QtCore.Qt.TransformationMode.SmoothTransformation)
|
|
99
|
+
ir.qimage = self.qimage.scaled(width, height, QtCore.Qt.AspectRatioMode.KeepAspectRatio if keepAspectRatio else QtCore.Qt.IgnoreAspectRatio, method)
|
|
100
|
+
return ir
|
|
68
101
|
|
|
69
102
|
class Canvas(QtBaseWidget):
|
|
70
103
|
def __init__(self, painter, *args):
|
|
@@ -286,3 +319,27 @@ class Canvas(QtBaseWidget):
|
|
|
286
319
|
self.drawPolyline(shape.coords, color=stroke, width=width)
|
|
287
320
|
else:
|
|
288
321
|
raise RuntimeError(f"Not implemented: drawShapely({type(shape).__name__}) {dir(shape)}")
|
|
322
|
+
|
|
323
|
+
def loadImage(self, image_path):
|
|
324
|
+
return ImageResource.load(image_path)
|
|
325
|
+
|
|
326
|
+
def drawImage(self, image, x=0, y=0, width=None, height=None, src_x=0, src_y=0, src_width=None, src_height=None, opacity=1.0):
|
|
327
|
+
if image.qimage.isNull():
|
|
328
|
+
return
|
|
329
|
+
|
|
330
|
+
if src_width is None:
|
|
331
|
+
src_width = image.qimage.width() - src_x
|
|
332
|
+
if src_height is None:
|
|
333
|
+
src_height = image.qimage.height() - src_y
|
|
334
|
+
|
|
335
|
+
source_rect = QtCore.QRect(src_x, src_y, src_width, src_height)
|
|
336
|
+
|
|
337
|
+
if width is None:
|
|
338
|
+
width = src_width
|
|
339
|
+
if height is None:
|
|
340
|
+
height = src_height
|
|
341
|
+
|
|
342
|
+
dest_rect = QtCore.QRect(x, y, width, height)
|
|
343
|
+
self.qpainter.setOpacity(opacity)
|
|
344
|
+
self.qpainter.drawImage(dest_rect, image.qimage, source_rect)
|
|
345
|
+
self.qpainter.setOpacity(1.0)
|
|
@@ -24,15 +24,18 @@ class ComboBox(QtBaseWidget):
|
|
|
24
24
|
super().update(prev)
|
|
25
25
|
|
|
26
26
|
def postSync(self):
|
|
27
|
+
index = -1
|
|
28
|
+
text = ""
|
|
29
|
+
|
|
27
30
|
if self.index_model:
|
|
28
31
|
index = self.index_model.value
|
|
29
32
|
text = self.children[index].text
|
|
30
33
|
elif self.text_model:
|
|
31
34
|
text = str(self.text_model.value)
|
|
32
35
|
try:
|
|
33
|
-
index = [c.
|
|
36
|
+
index = [c.value for c in self.children].index(text)
|
|
34
37
|
except:
|
|
35
|
-
index =
|
|
38
|
+
index = -1
|
|
36
39
|
|
|
37
40
|
if self.signal_connected:
|
|
38
41
|
self.ui.currentIndexChanged.disconnect()
|
|
@@ -51,11 +54,15 @@ class ComboBox(QtBaseWidget):
|
|
|
51
54
|
def on_currentIndexChanged(self, idx):
|
|
52
55
|
if self.index_model:
|
|
53
56
|
self.index_model.value = idx
|
|
57
|
+
if self.text_model:
|
|
58
|
+
self.text_model.value = self.children[idx].value
|
|
54
59
|
e = PUIEvent()
|
|
55
60
|
e.value = idx
|
|
56
61
|
self._change(e)
|
|
57
62
|
|
|
58
63
|
def on_currentTextChanged(self, text):
|
|
64
|
+
if not self.editable:
|
|
65
|
+
return
|
|
59
66
|
if self.text_model:
|
|
60
67
|
self.text_model.value = text
|
|
61
68
|
e = PUIEvent()
|
|
@@ -69,7 +76,10 @@ class ComboBox(QtBaseWidget):
|
|
|
69
76
|
self.ui.removeItem(idx)
|
|
70
77
|
|
|
71
78
|
class ComboBoxItem(PUINode):
|
|
72
|
-
def __init__(self, text):
|
|
79
|
+
def __init__(self, text, value=None):
|
|
73
80
|
super().__init__()
|
|
74
81
|
self.id(text)
|
|
75
82
|
self.text = text
|
|
83
|
+
if value is None:
|
|
84
|
+
value = text
|
|
85
|
+
self.value = value
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from .. import *
|
|
2
2
|
from .base import *
|
|
3
|
+
from .label import ClickableQLabel
|
|
4
|
+
from PySide6.QtWidgets import QSizePolicy
|
|
5
|
+
import os
|
|
3
6
|
|
|
4
7
|
class Image(QtBaseWidget):
|
|
5
8
|
def __init__(self, path):
|
|
@@ -10,13 +13,28 @@ class Image(QtBaseWidget):
|
|
|
10
13
|
def update(self, prev):
|
|
11
14
|
if prev and prev.ui:
|
|
12
15
|
self.ui = prev.ui
|
|
16
|
+
self.eventFilter = prev.eventFilter
|
|
13
17
|
self.curr_path = prev.curr_path
|
|
18
|
+
self.curr_path_mtime = prev.curr_path_mtime
|
|
14
19
|
self.pixmap = prev.pixmap
|
|
15
20
|
else:
|
|
16
|
-
self.ui =
|
|
21
|
+
self.ui = ClickableQLabel()
|
|
22
|
+
self.ui.clicked.connect(self._clicked)
|
|
17
23
|
self.curr_path = Prop()
|
|
24
|
+
self.curr_path_mtime = Prop()
|
|
18
25
|
|
|
19
|
-
if self.
|
|
26
|
+
if self._onClicked:
|
|
27
|
+
self.ui.setCursor(QtCore.Qt.PointingHandCursor)
|
|
28
|
+
|
|
29
|
+
if self.layout_weight:
|
|
30
|
+
# XXX keep aspect ratio
|
|
31
|
+
self.ui.setScaledContents(True)
|
|
32
|
+
self.ui.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Ignored, QSizePolicy.Policy.Ignored))
|
|
33
|
+
else:
|
|
34
|
+
self.ui.setScaledContents(False)
|
|
35
|
+
self.ui.setSizePolicy(QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred))
|
|
36
|
+
|
|
37
|
+
if self.curr_path.set(self.path) or self.curr_path_mtime.set(os.path.getmtime(self.path)):
|
|
20
38
|
self.pixmap = QtGui.QPixmap(self.path)
|
|
21
39
|
if self.layout_width is not None and self.layout_height is not None:
|
|
22
40
|
self.pixmap = self.pixmap.scaled(self.layout_width, self.layout_height, QtCore.Qt.KeepAspectRatio, mode=QtCore.Qt.SmoothTransformation)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from .base import *
|
|
3
|
+
|
|
4
|
+
class Stack(QtBaseLayout):
|
|
5
|
+
pui_terminal = False
|
|
6
|
+
pui_reversed_order = True
|
|
7
|
+
|
|
8
|
+
def __init__(self):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.qt_params = {}
|
|
11
|
+
if not isinstance(self.non_virtual_parent, QtBaseLayout):
|
|
12
|
+
self.layout_padding = (11,11,11,11)
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def outer(self):
|
|
16
|
+
return self.ui
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def inner(self):
|
|
20
|
+
return self.layout
|
|
21
|
+
|
|
22
|
+
def destroy(self, direct):
|
|
23
|
+
self.layout = None
|
|
24
|
+
super().destroy(direct)
|
|
25
|
+
|
|
26
|
+
def update(self, prev=None):
|
|
27
|
+
if prev and prev.ui:
|
|
28
|
+
self.ui = prev.ui
|
|
29
|
+
self.qtlayout = prev.qtlayout
|
|
30
|
+
else:
|
|
31
|
+
self.ui = QtWidgets.QWidget()
|
|
32
|
+
self.qtlayout = QtWidgets.QStackedLayout()
|
|
33
|
+
self.qtlayout.setStackingMode(QtWidgets.QStackedLayout.StackAll)
|
|
34
|
+
self.qtlayout.setContentsMargins(0,0,0,0)
|
|
35
|
+
self.ui.setLayout(self.qtlayout)
|
|
36
|
+
super().update(prev)
|
|
37
|
+
|
|
38
|
+
def addChild(self, idx, child):
|
|
39
|
+
from .modal import Modal
|
|
40
|
+
from .layout import Spacer
|
|
41
|
+
if isinstance(child, Spacer):
|
|
42
|
+
pass
|
|
43
|
+
elif isinstance(child, Modal):
|
|
44
|
+
pass
|
|
45
|
+
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
46
|
+
self.qtlayout.insertWidget(idx, child.outer)
|
|
47
|
+
self.mounted_children.insert(idx, child)
|
|
48
|
+
|
|
49
|
+
def removeChild(self, idx, child):
|
|
50
|
+
from .modal import Modal
|
|
51
|
+
from .layout import Spacer
|
|
52
|
+
if isinstance(child, Spacer):
|
|
53
|
+
pass
|
|
54
|
+
elif isinstance(child, Modal):
|
|
55
|
+
pass
|
|
56
|
+
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
57
|
+
child.outer.setParent(None)
|
|
58
|
+
self.mounted_children.pop(idx)
|
|
59
|
+
|
|
60
|
+
def postUpdate(self):
|
|
61
|
+
if self.ui:
|
|
62
|
+
if self._onDropped:
|
|
63
|
+
self.ui.setAcceptDrops(True)
|
|
64
|
+
self.ui.installEventFilter(self.eventFilter)
|
|
65
|
+
else:
|
|
66
|
+
self.ui.setAcceptDrops(False)
|
|
67
|
+
|
|
68
|
+
super().postUpdate()
|
|
69
|
+
|
|
70
|
+
class HBox(QtBaseLayout):
|
|
71
|
+
container_x = True
|
|
72
|
+
def update(self, prev):
|
|
73
|
+
if prev and prev.ui:
|
|
74
|
+
self.ui = prev.ui
|
|
75
|
+
self.qtlayout = prev.qtlayout
|
|
76
|
+
else:
|
|
77
|
+
self.ui = QtWidgets.QWidget()
|
|
78
|
+
self.qtlayout = QtWidgets.QHBoxLayout()
|
|
79
|
+
self.qtlayout.setContentsMargins(0,0,0,0)
|
|
80
|
+
self.ui.setLayout(self.qtlayout)
|
|
81
|
+
super().update(prev)
|
|
82
|
+
|
|
83
|
+
class VBox(QtBaseLayout):
|
|
84
|
+
container_y = True
|
|
85
|
+
def update(self, prev):
|
|
86
|
+
if prev and prev.ui:
|
|
87
|
+
self.ui = prev.ui
|
|
88
|
+
self.qtlayout = prev.qtlayout
|
|
89
|
+
else:
|
|
90
|
+
self.ui = QtWidgets.QWidget()
|
|
91
|
+
self.qtlayout = QtWidgets.QVBoxLayout()
|
|
92
|
+
self.qtlayout.setContentsMargins(0,0,0,0)
|
|
93
|
+
self.ui.setLayout(self.qtlayout)
|
|
94
|
+
super().update(prev)
|
|
95
|
+
|
|
96
|
+
class Spacer(PUINode):
|
|
97
|
+
pui_terminal = True
|
|
98
|
+
pui_movable = False
|
|
99
|
+
|
|
100
|
+
def update(self, prev):
|
|
101
|
+
if prev and prev.ui:
|
|
102
|
+
self.ui = prev.ui
|
|
103
|
+
else:
|
|
104
|
+
if isinstance(self.non_virtual_parent, VBox):
|
|
105
|
+
self.ui = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Expanding)
|
|
106
|
+
elif isinstance(self.non_virtual_parent, HBox):
|
|
107
|
+
self.ui = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
|
|
108
|
+
else:
|
|
109
|
+
self.ui = QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
|
110
|
+
super().update(prev)
|
|
111
|
+
|
|
112
|
+
def destroy(self, direct):
|
|
113
|
+
# self.ui.deleteLater() # QSpacerItem doesn't have .deleteLater()
|
|
114
|
+
self.ui = None
|
|
115
|
+
super().destroy(direct)
|
|
116
|
+
|
|
117
|
+
class Grid(QtBaseLayout):
|
|
118
|
+
pui_grid_layout = True
|
|
119
|
+
|
|
120
|
+
def update(self, prev):
|
|
121
|
+
if prev and prev.ui:
|
|
122
|
+
self.ui = prev.ui
|
|
123
|
+
self.layout = prev.layout
|
|
124
|
+
else:
|
|
125
|
+
self.ui = QtWidgets.QWidget()
|
|
126
|
+
self.layout = QtWidgets.QGridLayout()
|
|
127
|
+
self.layout.setContentsMargins(0,0,0,0)
|
|
128
|
+
self.ui.setLayout(self.layout)
|
|
129
|
+
super().update(prev)
|
|
130
|
+
|
|
131
|
+
def addChild(self, idx, child):
|
|
132
|
+
if isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
133
|
+
self.layout.addWidget(child.outer, child.grid_row, child.grid_column, child.grid_rowspan or 1, child.grid_columnspan or 1)
|
|
134
|
+
elif child.children:
|
|
135
|
+
self.addChild(idx, child.children[0])
|
|
136
|
+
|
|
137
|
+
def removeChild(self, idx, child):
|
|
138
|
+
if isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
139
|
+
child.outer.setParent(None)
|
|
140
|
+
elif child.children:
|
|
141
|
+
self.removeChild(idx, child.children[0])
|
|
@@ -75,7 +75,6 @@ class Scroll(QtBaseWidget):
|
|
|
75
75
|
if node.destroyed:
|
|
76
76
|
return
|
|
77
77
|
node.children[0].outer.origResizeEvent(event)
|
|
78
|
-
node.children[0].outer.resizeEvent = self.onUiResized
|
|
79
78
|
if node.horizontal is False:
|
|
80
79
|
if isinstance(node.children[0], QtBaseLayout):
|
|
81
80
|
node.outer.setMinimumWidth(node.children[0].outer.sizeHint().width())
|
|
@@ -88,6 +87,9 @@ class Scroll(QtBaseWidget):
|
|
|
88
87
|
elif isinstance(node.children[0], QtBaseWidget):
|
|
89
88
|
node.outer.setMinimumHeight(node.children[0].outer.sizeHint().height())
|
|
90
89
|
|
|
90
|
+
def postSync(self):
|
|
91
|
+
self.children[0].outer.resizeEvent = self.onUiResized
|
|
92
|
+
|
|
91
93
|
def scrollX(self, pos=0):
|
|
92
94
|
if math.copysign(1, pos) >= 0:
|
|
93
95
|
self.align_x = 0
|