qpuiq 0.14__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.14/qpuiq.egg-info → qpuiq-0.24}/PKG-INFO +5 -21
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/base.py +36 -15
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/canvas.py +46 -25
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/combobox.py +2 -2
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/divider.py +2 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/layout.py +69 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/scroll.py +3 -1
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/textfield.py +3 -5
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/window.py +4 -3
- {qpuiq-0.14 → qpuiq-0.24}/PUI/__init__.py +1 -1
- {qpuiq-0.14 → qpuiq-0.24}/PUI/dom.py +37 -12
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/__init__.py +1 -0
- qpuiq-0.24/PUI/flet/divider.py +14 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/textfield.py +3 -5
- {qpuiq-0.14 → qpuiq-0.24}/PUI/node.py +13 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/state.py +12 -2
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/application.py +2 -2
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/textfield.py +10 -6
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/textfield.py +3 -5
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/base.py +2 -2
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/combobox.py +2 -2
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/progressbar.py +2 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/textfield.py +15 -6
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/window.py +6 -0
- {qpuiq-0.14 → qpuiq-0.24/QPUIQ.egg-info}/PKG-INFO +5 -21
- {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/SOURCES.txt +5 -0
- {qpuiq-0.14 → qpuiq-0.24}/README.md +3 -12
- {qpuiq-0.14 → qpuiq-0.24}/LICENSE.txt +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/__init__.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/application.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/button.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/checkbox.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/dialog.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/image.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/label.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/matplotlib.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/mdi.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/menu.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/modal.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/progressbar.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/radiobutton.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/splitter.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/tab.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/table.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/text.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/toolbar.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/tree.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/common.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/decorator.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/application.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/base.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/button.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/canvas.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/checkbox.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/label.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/layout.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/progressbar.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/radiobutton.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/scroll.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/tab.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/text.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/window.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/interfaces.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/__init__.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/base.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/button.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/checkbox.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/label.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/layout.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/progressbar.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/radiobutton.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/scroll.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/tab.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/text.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/window.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/timeline.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/__init__.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/application.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/base.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/button.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/canvas.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/checkbox.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/label.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/layout.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/progressbar.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/radiobutton.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/scroll.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/tab.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/text.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/window.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/utils.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/view.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/__init__.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/application.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/button.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/canvas.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/checkbox.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/dialog.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/divider.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/label.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/layout.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/radiobutton.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/scroll.py +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/text.py +0 -0
- {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/dependency_links.txt +0 -0
- {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/top_level.txt +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/setup.cfg +0 -0
- {qpuiq-0.14 → qpuiq-0.24}/setup.py +0 -0
|
@@ -1,19 +1,12 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
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
|
|
7
7
|
Author-email: buganini@b612.tw
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE.txt
|
|
10
|
-
Dynamic: author
|
|
11
|
-
Dynamic: author-email
|
|
12
|
-
Dynamic: description
|
|
13
|
-
Dynamic: description-content-type
|
|
14
|
-
Dynamic: home-page
|
|
15
|
-
Dynamic: license-file
|
|
16
|
-
Dynamic: summary
|
|
17
10
|
|
|
18
11
|
# What is PUI
|
|
19
12
|
PUI is a reactive/declarative UI framework with two-way data binding.
|
|
@@ -213,38 +206,29 @@ Then PUI will take care of view update [(code)](https://github.com/buganini/PUI/
|
|
|
213
206
|
* no canvas
|
|
214
207
|
|
|
215
208
|
# Documents
|
|
216
|
-
* [Components Reference](
|
|
217
|
-
* [
|
|
209
|
+
* [Components Reference](https://buganini.github.io/PUI/doc/Reference.html)
|
|
210
|
+
* [Layout Strategy](https://buganini.github.io/PUI/doc/Layout.html)
|
|
218
211
|
|
|
219
212
|
# Used by
|
|
220
|
-
* https://github.com/buganini/
|
|
213
|
+
* https://github.com/buganini/Kikakuka
|
|
221
214
|
* https://github.com/solvcon/modmesh
|
|
222
215
|
|
|
223
216
|
# TODO
|
|
224
|
-
* Merge node and view
|
|
225
217
|
* Dump with layout parameters and add test cases
|
|
226
218
|
* [Toga](https://beeware.org/project/projects/libraries/toga/)
|
|
227
|
-
* [ISSUE] textual layout sizing (cookbook scroll example)
|
|
228
219
|
* [ISSUE] flet layout sizing (cookbook scroll example)
|
|
229
220
|
* nested state trigger
|
|
230
221
|
* set state in PUIView __init__
|
|
231
222
|
* set state in setup() ?
|
|
232
223
|
* Tabs(`tabposition`)
|
|
233
224
|
* Lazy List
|
|
234
|
-
* StateObject decorator
|
|
235
225
|
* UI Flow
|
|
236
226
|
* Navigation Stack
|
|
237
227
|
* View Router
|
|
238
228
|
* Model Window/Dialog
|
|
239
229
|
* Layout
|
|
240
|
-
* ZBox
|
|
241
230
|
* SwiftUI style overlay ??
|
|
242
231
|
* Canvas
|
|
243
|
-
* Rect
|
|
244
232
|
* Arc
|
|
245
|
-
* Image
|
|
246
233
|
* ...
|
|
247
|
-
* Table
|
|
248
|
-
* Tree
|
|
249
|
-
* Dialog
|
|
250
234
|
* State with Pydantic support?
|
|
@@ -132,17 +132,17 @@ class QtBaseWidget(PUINode):
|
|
|
132
132
|
|
|
133
133
|
def handleDragEnterEvent(self, event):
|
|
134
134
|
if self._onDragEntered:
|
|
135
|
-
self._onDragEntered[0](event, *self._onDragEntered[1], **self._onDragEntered[2])
|
|
135
|
+
return self._onDragEntered[0](event, *self._onDragEntered[1], **self._onDragEntered[2])
|
|
136
136
|
else:
|
|
137
|
-
event.
|
|
138
|
-
|
|
137
|
+
event.ignore()
|
|
138
|
+
return True
|
|
139
139
|
|
|
140
140
|
def handleDropEvent(self, event):
|
|
141
141
|
if self._onDropped:
|
|
142
|
-
self._onDropped[0](event, *self._onDropped[1], **self._onDropped[2])
|
|
142
|
+
return self._onDropped[0](event, *self._onDropped[1], **self._onDropped[2])
|
|
143
143
|
else:
|
|
144
|
-
print("Dropped", event)
|
|
145
144
|
event.ignore()
|
|
145
|
+
return False
|
|
146
146
|
|
|
147
147
|
def qtSizeHint(self):
|
|
148
148
|
node = self.get_node()
|
|
@@ -162,7 +162,11 @@ class QtBaseWidget(PUINode):
|
|
|
162
162
|
self.qt_params[k] = v
|
|
163
163
|
return self
|
|
164
164
|
|
|
165
|
-
class QtBaseLayout(
|
|
165
|
+
class QtBaseLayout(QtBaseWidget):
|
|
166
|
+
pui_terminal = False
|
|
167
|
+
container_x = False
|
|
168
|
+
container_y = False
|
|
169
|
+
|
|
166
170
|
def __init__(self):
|
|
167
171
|
super().__init__()
|
|
168
172
|
self.qt_params = {}
|
|
@@ -178,22 +182,23 @@ class QtBaseLayout(PUINode):
|
|
|
178
182
|
return self.layout
|
|
179
183
|
|
|
180
184
|
def destroy(self, direct):
|
|
181
|
-
if direct:
|
|
182
|
-
if self.ui:
|
|
183
|
-
self.ui.deleteLater()
|
|
184
185
|
self.layout = None
|
|
185
|
-
self.ui = None
|
|
186
186
|
super().destroy(direct)
|
|
187
187
|
|
|
188
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
|
+
|
|
189
194
|
super().update(prev)
|
|
190
|
-
_apply_params(self.ui, self)
|
|
191
195
|
|
|
192
196
|
def addChild(self, idx, child):
|
|
193
197
|
from .modal import Modal
|
|
194
198
|
from .layout import Spacer
|
|
195
199
|
if isinstance(child, Spacer):
|
|
196
200
|
self.qtlayout.insertItem(idx, child.outer)
|
|
201
|
+
self.mounted_children.insert(idx, child)
|
|
197
202
|
elif isinstance(child, Modal):
|
|
198
203
|
pass
|
|
199
204
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
@@ -201,21 +206,37 @@ class QtBaseLayout(PUINode):
|
|
|
201
206
|
if not child.layout_weight is None:
|
|
202
207
|
params["stretch"] = child.layout_weight
|
|
203
208
|
self.qtlayout.insertWidget(idx, child.outer, **params)
|
|
209
|
+
self.mounted_children.insert(idx, child)
|
|
204
210
|
|
|
205
211
|
def removeChild(self, idx, child):
|
|
206
212
|
from .modal import Modal
|
|
207
213
|
from .layout import Spacer
|
|
208
214
|
if isinstance(child, Spacer):
|
|
209
215
|
self.qtlayout.removeItem(child.outer)
|
|
216
|
+
self.mounted_children.pop(idx)
|
|
210
217
|
elif isinstance(child, Modal):
|
|
211
218
|
pass
|
|
212
219
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
213
220
|
child.outer.setParent(None)
|
|
221
|
+
self.mounted_children.pop(idx)
|
|
214
222
|
|
|
215
|
-
def
|
|
216
|
-
|
|
217
|
-
self.
|
|
218
|
-
|
|
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)
|
|
219
240
|
|
|
220
241
|
class QtBaseFrame(QtBaseWidget):
|
|
221
242
|
pui_terminal = False
|
|
@@ -6,7 +6,7 @@ from PySide6.QtGui import QPainter, QColor, QPainterPath, QImage
|
|
|
6
6
|
|
|
7
7
|
class PUIQtCanvas(QtWidgets.QWidget):
|
|
8
8
|
def __init__(self, node, width=None, height=None):
|
|
9
|
-
self.
|
|
9
|
+
self.puinode = node
|
|
10
10
|
self.width = width
|
|
11
11
|
self.height = height
|
|
12
12
|
super().__init__()
|
|
@@ -18,25 +18,25 @@ class PUIQtCanvas(QtWidgets.QWidget):
|
|
|
18
18
|
e = PUIEvent()
|
|
19
19
|
e.button = event.button().value
|
|
20
20
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
21
|
-
self.
|
|
21
|
+
self.puinode._dblclicked(e)
|
|
22
22
|
|
|
23
23
|
def mousePressEvent(self, event):
|
|
24
24
|
e = PUIEvent()
|
|
25
25
|
e.button = event.button().value
|
|
26
26
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
27
|
-
self.
|
|
27
|
+
self.puinode._mousedown(e)
|
|
28
28
|
|
|
29
29
|
def mouseReleaseEvent(self, event):
|
|
30
30
|
e = PUIEvent()
|
|
31
31
|
e.button = event.button().value
|
|
32
32
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
33
|
-
self.
|
|
33
|
+
self.puinode._mouseup(e)
|
|
34
34
|
|
|
35
35
|
def mouseMoveEvent(self, event):
|
|
36
36
|
e = PUIEvent()
|
|
37
37
|
e.button = event.button().value
|
|
38
38
|
e.x, e.y = event.position().toPoint().toTuple()
|
|
39
|
-
self.
|
|
39
|
+
self.puinode._mousemove(e)
|
|
40
40
|
|
|
41
41
|
def wheelEvent(self, event):
|
|
42
42
|
e = PUIEvent()
|
|
@@ -56,27 +56,48 @@ class PUIQtCanvas(QtWidgets.QWidget):
|
|
|
56
56
|
if emodifiers & QtCore.Qt.MetaModifier:
|
|
57
57
|
modifier |= KeyModifier.META
|
|
58
58
|
e.modifiers = modifier
|
|
59
|
-
self.
|
|
59
|
+
self.puinode._wheel(e)
|
|
60
60
|
|
|
61
61
|
def paintEvent(self, event):
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
puinode = self.puinode.get_node()
|
|
63
|
+
puinode.qpainter = QPainter()
|
|
64
|
+
puinode.qpainter.begin(self)
|
|
65
|
+
puinode.qpainter.setRenderHints(QtGui.QPainter.Antialiasing, True)
|
|
66
66
|
|
|
67
|
-
if not
|
|
67
|
+
if not puinode.style_bgcolor is None:
|
|
68
68
|
bgBrush = QtGui.QBrush()
|
|
69
|
-
bgBrush.setColor(QtGui.QColor(
|
|
69
|
+
bgBrush.setColor(QtGui.QColor(puinode.style_bgcolor))
|
|
70
70
|
bgBrush.setStyle(QtCore.Qt.SolidPattern)
|
|
71
71
|
rect = QtCore.QRect(0, 0, self.width or self.geometry().width(), self.height or self.geometry().height())
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
|
80
101
|
|
|
81
102
|
class Canvas(QtBaseWidget):
|
|
82
103
|
def __init__(self, painter, *args):
|
|
@@ -300,16 +321,16 @@ class Canvas(QtBaseWidget):
|
|
|
300
321
|
raise RuntimeError(f"Not implemented: drawShapely({type(shape).__name__}) {dir(shape)}")
|
|
301
322
|
|
|
302
323
|
def loadImage(self, image_path):
|
|
303
|
-
return
|
|
324
|
+
return ImageResource.load(image_path)
|
|
304
325
|
|
|
305
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):
|
|
306
|
-
if image.isNull():
|
|
327
|
+
if image.qimage.isNull():
|
|
307
328
|
return
|
|
308
329
|
|
|
309
330
|
if src_width is None:
|
|
310
|
-
src_width = image.width() - src_x
|
|
331
|
+
src_width = image.qimage.width() - src_x
|
|
311
332
|
if src_height is None:
|
|
312
|
-
src_height = image.height() - src_y
|
|
333
|
+
src_height = image.qimage.height() - src_y
|
|
313
334
|
|
|
314
335
|
source_rect = QtCore.QRect(src_x, src_y, src_width, src_height)
|
|
315
336
|
|
|
@@ -320,5 +341,5 @@ class Canvas(QtBaseWidget):
|
|
|
320
341
|
|
|
321
342
|
dest_rect = QtCore.QRect(x, y, width, height)
|
|
322
343
|
self.qpainter.setOpacity(opacity)
|
|
323
|
-
self.qpainter.drawImage(dest_rect, image, source_rect)
|
|
344
|
+
self.qpainter.drawImage(dest_rect, image.qimage, source_rect)
|
|
324
345
|
self.qpainter.setOpacity(1.0)
|
|
@@ -24,7 +24,7 @@ class ComboBox(QtBaseWidget):
|
|
|
24
24
|
super().update(prev)
|
|
25
25
|
|
|
26
26
|
def postSync(self):
|
|
27
|
-
index =
|
|
27
|
+
index = -1
|
|
28
28
|
text = ""
|
|
29
29
|
|
|
30
30
|
if self.index_model:
|
|
@@ -35,7 +35,7 @@ class ComboBox(QtBaseWidget):
|
|
|
35
35
|
try:
|
|
36
36
|
index = [c.value for c in self.children].index(text)
|
|
37
37
|
except:
|
|
38
|
-
index =
|
|
38
|
+
index = -1
|
|
39
39
|
|
|
40
40
|
if self.signal_connected:
|
|
41
41
|
self.ui.currentIndexChanged.disconnect()
|
|
@@ -1,7 +1,74 @@
|
|
|
1
1
|
from .. import *
|
|
2
2
|
from .base import *
|
|
3
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
|
+
|
|
4
70
|
class HBox(QtBaseLayout):
|
|
71
|
+
container_x = True
|
|
5
72
|
def update(self, prev):
|
|
6
73
|
if prev and prev.ui:
|
|
7
74
|
self.ui = prev.ui
|
|
@@ -14,6 +81,7 @@ class HBox(QtBaseLayout):
|
|
|
14
81
|
super().update(prev)
|
|
15
82
|
|
|
16
83
|
class VBox(QtBaseLayout):
|
|
84
|
+
container_y = True
|
|
17
85
|
def update(self, prev):
|
|
18
86
|
if prev and prev.ui:
|
|
19
87
|
self.ui = prev.ui
|
|
@@ -27,6 +95,7 @@ class VBox(QtBaseLayout):
|
|
|
27
95
|
|
|
28
96
|
class Spacer(PUINode):
|
|
29
97
|
pui_terminal = True
|
|
98
|
+
pui_movable = False
|
|
30
99
|
|
|
31
100
|
def update(self, prev):
|
|
32
101
|
if prev and prev.ui:
|
|
@@ -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
|
|
@@ -35,7 +35,7 @@ class TextField(QtBaseWidget):
|
|
|
35
35
|
|
|
36
36
|
super().update(prev)
|
|
37
37
|
|
|
38
|
-
def on_editing_finished(self):
|
|
38
|
+
def on_editing_finished(self): # finish editing
|
|
39
39
|
node = self.get_node()
|
|
40
40
|
node.editing = False
|
|
41
41
|
value = self.ui.text()
|
|
@@ -51,14 +51,12 @@ class TextField(QtBaseWidget):
|
|
|
51
51
|
self._change(e)
|
|
52
52
|
node.ui.clearFocus()
|
|
53
53
|
|
|
54
|
-
def on_textchanged(self):
|
|
54
|
+
def on_textchanged(self): # editing
|
|
55
55
|
node = self.get_node()
|
|
56
|
+
self.editing = True
|
|
56
57
|
value = self.ui.text()
|
|
57
58
|
if node.edit_model:
|
|
58
|
-
node.editing = True
|
|
59
59
|
node.edit_model.value = value
|
|
60
|
-
else:
|
|
61
|
-
node.model.value = value
|
|
62
60
|
e = PUIEvent()
|
|
63
61
|
e.value = value
|
|
64
62
|
self._input(e)
|
|
@@ -9,7 +9,7 @@ class QMainWindow(QtWidgets.QMainWindow):
|
|
|
9
9
|
def keyPressEvent(self, event):
|
|
10
10
|
e = PUIEvent()
|
|
11
11
|
e.text = event.text()
|
|
12
|
-
self.
|
|
12
|
+
self.puinode._keypress(e)
|
|
13
13
|
|
|
14
14
|
def mousePressEvent(self, event):
|
|
15
15
|
focused_widget = QtWidgets.QApplication.focusWidget()
|
|
@@ -34,13 +34,13 @@ class Window(QtBaseWidget):
|
|
|
34
34
|
def update(self, prev=None):
|
|
35
35
|
if prev and prev.ui:
|
|
36
36
|
self.ui = prev.ui
|
|
37
|
-
self.ui.
|
|
37
|
+
self.ui.puinode = self
|
|
38
38
|
self.curr_size = prev.curr_size
|
|
39
39
|
self.curr_maximize = prev.curr_maximize
|
|
40
40
|
self.curr_fullscreen = prev.curr_fullscreen
|
|
41
41
|
else:
|
|
42
42
|
self.ui = QMainWindow()
|
|
43
|
-
self.ui.
|
|
43
|
+
self.ui.puinode = self
|
|
44
44
|
self.ui.show()
|
|
45
45
|
self.curr_size = Prop()
|
|
46
46
|
self.curr_maximize = Prop()
|
|
@@ -49,6 +49,7 @@ class Window(QtBaseWidget):
|
|
|
49
49
|
if self.curr_size.set(self.size):
|
|
50
50
|
self.ui.resize(*self.size)
|
|
51
51
|
if self.curr_maximize.set(self.maximize):
|
|
52
|
+
self.ui.resize(800, 600) # workaround on windows ref: https://stackoverflow.com/questions/27157312/qt-showmaximized-not-working-in-windows
|
|
52
53
|
self.ui.showMaximized()
|
|
53
54
|
if self.curr_fullscreen.set(self.fullscreen):
|
|
54
55
|
self.ui.showFullScreen()
|
|
@@ -17,6 +17,8 @@ def sortGridDOMInPlace(dom):
|
|
|
17
17
|
dom.sort(key=lambda c:(c.grid_row, c.grid_column, c.grid_rowspan, c.grid_columnspan))
|
|
18
18
|
|
|
19
19
|
def dom_remove_node(dom_parent, dom_offset, child):
|
|
20
|
+
if DEBUG:
|
|
21
|
+
print(f"dom_remove_node key={child.key} virtual={child.pui_virtual} children={len(child.children)} dom_parent={dom_parent.key} dom_offset={dom_offset}")
|
|
20
22
|
if child.pui_virtual:
|
|
21
23
|
ret = [child]
|
|
22
24
|
for c in child.children:
|
|
@@ -44,20 +46,35 @@ def sync(node, dom_parent, dom_offset, oldVDOM, newVDOM, depth=0):
|
|
|
44
46
|
dom_children_curr = 0
|
|
45
47
|
|
|
46
48
|
if DEBUG:
|
|
47
|
-
print(f"{(depth)*' '}Syncing {node.key}@{id(node)} parent={dom_parent.key}@{id(dom_parent)} dom_offset={dom_offset} dom_children_num={dom_children_num} old={len(oldVDOM)} new={len(newVDOM)}")
|
|
49
|
+
print(f"{(depth)*' '}Syncing {node.key}@{id(node)}#tag={node._tag} parent={dom_parent.key}@{id(dom_parent)}#tag={dom_parent._tag} dom_offset={dom_offset} dom_children_num={dom_children_num} old={len(oldVDOM)} new={len(newVDOM)}")
|
|
48
50
|
|
|
49
51
|
if node.pui_grid_layout:
|
|
50
52
|
sortGridDOMInPlace(oldVDOM)
|
|
51
53
|
sortGridDOMInPlace(newVDOM)
|
|
54
|
+
else:
|
|
55
|
+
oldOrdered = [c for c in oldVDOM if not c.pui_outoforder]
|
|
56
|
+
oldOutOfOrdered = [c for c in oldVDOM if c.pui_outoforder]
|
|
57
|
+
newOrdered = [c for c in newVDOM if not c.pui_outoforder]
|
|
58
|
+
newOutOfOrdered = [c for c in newVDOM if c.pui_outoforder]
|
|
59
|
+
|
|
60
|
+
if node.pui_reversed_order:
|
|
61
|
+
oldOrdered.reverse()
|
|
62
|
+
newOrdered.reverse()
|
|
63
|
+
|
|
64
|
+
oldVDOM.clear()
|
|
65
|
+
oldVDOM.extend(oldOrdered + oldOutOfOrdered)
|
|
66
|
+
newVDOM.clear()
|
|
67
|
+
newVDOM.extend(newOrdered + newOutOfOrdered)
|
|
52
68
|
|
|
53
69
|
if DEBUG:
|
|
54
70
|
print(f"{(depth+1)*' '}===OLD===")
|
|
55
71
|
for c in oldVDOM:
|
|
56
|
-
print(f"{(depth+1)*' '}{c.key} virtual={c.pui_virtual} ui={c.ui}")
|
|
72
|
+
print(f"{(depth+1)*' '}{c.key}#tag={c._tag} virtual={c.pui_virtual} children={len(c.children)} ui={c.ui}")
|
|
57
73
|
|
|
58
74
|
print(f"{(depth+1)*' '}===NEW===")
|
|
59
75
|
for c in newVDOM:
|
|
60
|
-
print(f"{(depth+1)*' '}{c.key} virtual={c.pui_virtual}")
|
|
76
|
+
print(f"{(depth+1)*' '}{c.key}#tag={c._tag} virtual={c.pui_virtual} children={len(c.children)}")
|
|
77
|
+
print(f"{(depth+1)*' '}=========")
|
|
61
78
|
|
|
62
79
|
oldVMap = [x.key for x in oldVDOM]
|
|
63
80
|
newVMap = [x.key for x in newVDOM]
|
|
@@ -110,7 +127,7 @@ def sync(node, dom_parent, dom_offset, oldVDOM, newVDOM, depth=0):
|
|
|
110
127
|
print(f"{(depth+2)*' '}dom_children_curr += 1 => {dom_children_curr} dom_children_num={dom_children_num}")
|
|
111
128
|
|
|
112
129
|
if not new.pui_terminal:
|
|
113
|
-
sync(new,
|
|
130
|
+
sync(new, old, 0, old.children, new.children, depth+1)
|
|
114
131
|
|
|
115
132
|
break # finish
|
|
116
133
|
|
|
@@ -134,10 +151,12 @@ def sync(node, dom_parent, dom_offset, oldVDOM, newVDOM, depth=0):
|
|
|
134
151
|
|
|
135
152
|
# Step 3. setup target node
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
154
|
+
matchedIdx = None
|
|
155
|
+
if new.pui_movable:
|
|
156
|
+
try:
|
|
157
|
+
matchedIdx = oldVMap[childIdx+1:].index(new.key) + childIdx + 1
|
|
158
|
+
except ValueError:
|
|
159
|
+
pass
|
|
141
160
|
|
|
142
161
|
## Step 3-1. new node
|
|
143
162
|
if matchedIdx is None:
|
|
@@ -197,11 +216,11 @@ def sync(node, dom_parent, dom_offset, oldVDOM, newVDOM, depth=0):
|
|
|
197
216
|
else:
|
|
198
217
|
if DEBUG:
|
|
199
218
|
print(f"{(depth+1)*' '}S3-2-2. MOVE {childIdx} {new.key}")
|
|
200
|
-
oldVMap.pop(matchedIdx)
|
|
201
|
-
old = oldVDOM.pop(matchedIdx)
|
|
202
219
|
found, offset = dom_parent.findDomOffsetForNode(old)
|
|
203
220
|
if not found:
|
|
204
|
-
raise VDomError(f"findDomOffsetForNode() failed for {old.key} on {dom_parent.key}")
|
|
221
|
+
raise VDomError(f"S3-2-2: findDomOffsetForNode() failed for {old.key}#tag={old._tag} on {dom_parent.key}#tag={dom_parent._tag} {dom_parent.children}")
|
|
222
|
+
oldVMap.pop(matchedIdx)
|
|
223
|
+
old = oldVDOM.pop(matchedIdx)
|
|
205
224
|
nodes = dom_remove_node(dom_parent, offset, old)
|
|
206
225
|
dom_add_nodes(dom_parent, dom_offset + dom_children_curr, nodes)
|
|
207
226
|
|
|
@@ -213,13 +232,19 @@ def sync(node, dom_parent, dom_offset, oldVDOM, newVDOM, depth=0):
|
|
|
213
232
|
break # finish
|
|
214
233
|
|
|
215
234
|
# Step 4. trim removed trail
|
|
235
|
+
if DEBUG:
|
|
236
|
+
print(f"{(depth+1)*' '}S4. TRIM")
|
|
216
237
|
nl = len(newVDOM)
|
|
238
|
+
if DEBUG:
|
|
239
|
+
print(f"{(depth+1)*' '}S4. TRIM", f"dom_offset={dom_offset}", len(oldVDOM), "=>", len(newVDOM))
|
|
217
240
|
while len(oldVDOM) > nl:
|
|
218
241
|
old = oldVDOM.pop(nl)
|
|
242
|
+
if DEBUG:
|
|
243
|
+
print(f"{(depth+2)*' '}", f"key={old.key} virtual={old.pui_virtual} children={len(old.children)}")
|
|
219
244
|
oldVMap.pop(nl)
|
|
220
245
|
nodes = dom_remove_node(dom_parent, dom_offset + nl, old)
|
|
221
246
|
dom_children_num -= len([n for n in nodes if not n.pui_virtual and not n.pui_outoforder])
|
|
222
|
-
toBeDeleted.
|
|
247
|
+
toBeDeleted.append(old)
|
|
223
248
|
|
|
224
249
|
for c in newVDOM:
|
|
225
250
|
c.postUpdate()
|
|
@@ -30,19 +30,17 @@ class TextField(FBase):
|
|
|
30
30
|
|
|
31
31
|
super().update(prev)
|
|
32
32
|
|
|
33
|
-
def on_textbox_changed(self, e):
|
|
33
|
+
def on_textbox_changed(self, e): # editing
|
|
34
34
|
node = self.get_node()
|
|
35
|
+
node.editing = True
|
|
35
36
|
value = e.control.value
|
|
36
37
|
if node.edit_model:
|
|
37
|
-
node.editing = True
|
|
38
38
|
node.edit_model.value = value
|
|
39
|
-
else:
|
|
40
|
-
node.model.value = value
|
|
41
39
|
e = PUIEvent()
|
|
42
40
|
e.value = value
|
|
43
41
|
self._input(e)
|
|
44
42
|
|
|
45
|
-
def on_change(self, e):
|
|
43
|
+
def on_change(self, e): # finish editing
|
|
46
44
|
node = self.get_node()
|
|
47
45
|
node.editing = False
|
|
48
46
|
value = e.control.value
|