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.
Files changed (108) hide show
  1. {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24}/PKG-INFO +5 -21
  2. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/base.py +36 -15
  3. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/canvas.py +46 -25
  4. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/combobox.py +2 -2
  5. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/divider.py +2 -0
  6. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/layout.py +69 -0
  7. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/scroll.py +3 -1
  8. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/textfield.py +3 -5
  9. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/window.py +4 -3
  10. {qpuiq-0.14 → qpuiq-0.24}/PUI/__init__.py +1 -1
  11. {qpuiq-0.14 → qpuiq-0.24}/PUI/dom.py +37 -12
  12. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/__init__.py +1 -0
  13. qpuiq-0.24/PUI/flet/divider.py +14 -0
  14. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/textfield.py +3 -5
  15. {qpuiq-0.14 → qpuiq-0.24}/PUI/node.py +13 -0
  16. {qpuiq-0.14 → qpuiq-0.24}/PUI/state.py +12 -2
  17. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/application.py +2 -2
  18. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/textfield.py +10 -6
  19. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/textfield.py +3 -5
  20. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/base.py +2 -2
  21. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/combobox.py +2 -2
  22. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/progressbar.py +2 -0
  23. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/textfield.py +15 -6
  24. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/window.py +6 -0
  25. {qpuiq-0.14 → qpuiq-0.24/QPUIQ.egg-info}/PKG-INFO +5 -21
  26. {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/SOURCES.txt +5 -0
  27. {qpuiq-0.14 → qpuiq-0.24}/README.md +3 -12
  28. {qpuiq-0.14 → qpuiq-0.24}/LICENSE.txt +0 -0
  29. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/__init__.py +0 -0
  30. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/application.py +0 -0
  31. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/button.py +0 -0
  32. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/checkbox.py +0 -0
  33. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/dialog.py +0 -0
  34. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/image.py +0 -0
  35. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/label.py +0 -0
  36. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/matplotlib.py +0 -0
  37. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/mdi.py +0 -0
  38. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/menu.py +0 -0
  39. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/modal.py +0 -0
  40. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/progressbar.py +0 -0
  41. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/radiobutton.py +0 -0
  42. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/splitter.py +0 -0
  43. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/tab.py +0 -0
  44. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/table.py +0 -0
  45. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/text.py +0 -0
  46. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/toolbar.py +0 -0
  47. {qpuiq-0.14 → qpuiq-0.24}/PUI/PySide6/tree.py +0 -0
  48. {qpuiq-0.14 → qpuiq-0.24}/PUI/common.py +0 -0
  49. {qpuiq-0.14 → qpuiq-0.24}/PUI/decorator.py +0 -0
  50. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/application.py +0 -0
  51. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/base.py +0 -0
  52. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/button.py +0 -0
  53. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/canvas.py +0 -0
  54. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/checkbox.py +0 -0
  55. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/label.py +0 -0
  56. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/layout.py +0 -0
  57. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/progressbar.py +0 -0
  58. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/radiobutton.py +0 -0
  59. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/scroll.py +0 -0
  60. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/tab.py +0 -0
  61. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/text.py +0 -0
  62. {qpuiq-0.14 → qpuiq-0.24}/PUI/flet/window.py +0 -0
  63. {qpuiq-0.14 → qpuiq-0.24}/PUI/interfaces.py +0 -0
  64. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/__init__.py +0 -0
  65. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/base.py +0 -0
  66. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/button.py +0 -0
  67. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/checkbox.py +0 -0
  68. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/label.py +0 -0
  69. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/layout.py +0 -0
  70. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/progressbar.py +0 -0
  71. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/radiobutton.py +0 -0
  72. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/scroll.py +0 -0
  73. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/tab.py +0 -0
  74. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/text.py +0 -0
  75. {qpuiq-0.14 → qpuiq-0.24}/PUI/textual/window.py +0 -0
  76. {qpuiq-0.14 → qpuiq-0.24}/PUI/timeline.py +0 -0
  77. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/__init__.py +0 -0
  78. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/application.py +0 -0
  79. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/base.py +0 -0
  80. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/button.py +0 -0
  81. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/canvas.py +0 -0
  82. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/checkbox.py +0 -0
  83. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/label.py +0 -0
  84. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/layout.py +0 -0
  85. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/progressbar.py +0 -0
  86. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/radiobutton.py +0 -0
  87. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/scroll.py +0 -0
  88. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/tab.py +0 -0
  89. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/text.py +0 -0
  90. {qpuiq-0.14 → qpuiq-0.24}/PUI/tkinter/window.py +0 -0
  91. {qpuiq-0.14 → qpuiq-0.24}/PUI/utils.py +0 -0
  92. {qpuiq-0.14 → qpuiq-0.24}/PUI/view.py +0 -0
  93. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/__init__.py +0 -0
  94. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/application.py +0 -0
  95. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/button.py +0 -0
  96. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/canvas.py +0 -0
  97. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/checkbox.py +0 -0
  98. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/dialog.py +0 -0
  99. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/divider.py +0 -0
  100. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/label.py +0 -0
  101. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/layout.py +0 -0
  102. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/radiobutton.py +0 -0
  103. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/scroll.py +0 -0
  104. {qpuiq-0.14 → qpuiq-0.24}/PUI/wx/text.py +0 -0
  105. {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/dependency_links.txt +0 -0
  106. {qpuiq-0.14/qpuiq.egg-info → qpuiq-0.24/QPUIQ.egg-info}/top_level.txt +0 -0
  107. {qpuiq-0.14 → qpuiq-0.24}/setup.cfg +0 -0
  108. {qpuiq-0.14 → qpuiq-0.24}/setup.py +0 -0
@@ -1,19 +1,12 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: qpuiq
3
- Version: 0.14
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](REFERENCE.md)
217
- * [Sizing Strategy](https://html-preview.github.io/?url=https://github.com/buganini/PUI/blob/main/doc/Sizing.html)
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/kikit-ui
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.setDropAction(QtCore.Qt.CopyAction)
138
- event.accept()
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(PUINode):
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 qt(self, **kwargs):
216
- for k,v in kwargs.items():
217
- self.qt_params[k] = v
218
- return self
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.node = node
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.node._dblclicked(e)
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.node._mousedown(e)
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.node._mouseup(e)
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.node._mousemove(e)
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.node._wheel(e)
59
+ self.puinode._wheel(e)
60
60
 
61
61
  def paintEvent(self, event):
62
- node = self.node.get_node()
63
- node.qpainter = QPainter()
64
- node.qpainter.begin(self)
65
- node.qpainter.setRenderHints(QtGui.QPainter.Antialiasing, True)
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 node.style_bgcolor is None:
67
+ if not puinode.style_bgcolor is None:
68
68
  bgBrush = QtGui.QBrush()
69
- bgBrush.setColor(QtGui.QColor(node.style_bgcolor))
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
- node.qpainter.fillRect(rect, bgBrush)
73
-
74
- node.width = self.geometry().width()
75
- node.height = self.geometry().height()
76
- node.painter(node, *node.args)
77
-
78
- node.qpainter.end()
79
- node.qpainter = None
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 QImage(image_path)
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 = 0
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 = 0
38
+ index = -1
39
39
 
40
40
  if self.signal_connected:
41
41
  self.ui.currentIndexChanged.disconnect()
@@ -3,6 +3,8 @@ from .base import *
3
3
  from .layout import *
4
4
 
5
5
  class Divider(QtBaseWidget):
6
+ pui_movable = False
7
+
6
8
  def __init__(self):
7
9
  super().__init__()
8
10
 
@@ -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.node._keypress(e)
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.node = self
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.node = self
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()
@@ -1,4 +1,4 @@
1
- __version__ = "0.14"
1
+ __version__ = "0.24"
2
2
 
3
3
  from .node import *
4
4
  from .view import *
@@ -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, new, 0, old.children, new.children, depth+1)
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
- try:
138
- matchedIdx = oldVMap[childIdx+1:].index(new.key) + childIdx + 1
139
- except ValueError:
140
- matchedIdx = None
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.extend(nodes)
247
+ toBeDeleted.append(old)
223
248
 
224
249
  for c in newVDOM:
225
250
  c.postUpdate()
@@ -2,6 +2,7 @@ from .application import *
2
2
  from .button import *
3
3
  from .canvas import *
4
4
  from .checkbox import *
5
+ from .divider import *
5
6
  from .label import *
6
7
  from .layout import *
7
8
  from .progressbar import *
@@ -0,0 +1,14 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class Divider(FBase):
5
+ def update(self, prev):
6
+ if prev and prev.ui:
7
+ self.ui = prev.ui
8
+ try:
9
+ self.ui.update()
10
+ except:
11
+ pass
12
+ else:
13
+ self.ui = ft.Divider()
14
+ self.ui.expand = self.layout_weight
@@ -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