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.
Files changed (112) hide show
  1. {qpuiq-0.10 → qpuiq-0.24}/PKG-INFO +21 -14
  2. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/base.py +76 -13
  3. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/canvas.py +77 -20
  4. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/combobox.py +13 -3
  5. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/divider.py +2 -0
  6. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/image.py +20 -2
  7. qpuiq-0.24/PUI/PySide6/layout.py +141 -0
  8. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/scroll.py +3 -1
  9. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/table.py +71 -13
  10. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/textfield.py +4 -4
  11. qpuiq-0.24/PUI/PySide6/tree.py +290 -0
  12. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/window.py +4 -3
  13. {qpuiq-0.10 → qpuiq-0.24}/PUI/__init__.py +1 -1
  14. {qpuiq-0.10 → qpuiq-0.24}/PUI/common.py +6 -0
  15. {qpuiq-0.10 → qpuiq-0.24}/PUI/dom.py +37 -12
  16. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/__init__.py +1 -0
  17. qpuiq-0.24/PUI/flet/divider.py +14 -0
  18. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/textfield.py +2 -2
  19. {qpuiq-0.10 → qpuiq-0.24}/PUI/node.py +25 -0
  20. {qpuiq-0.10 → qpuiq-0.24}/PUI/state.py +18 -5
  21. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/__init__.py +1 -0
  22. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/application.py +2 -2
  23. qpuiq-0.24/PUI/textual/base.py +148 -0
  24. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/label.py +2 -2
  25. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/layout.py +4 -0
  26. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/scroll.py +6 -4
  27. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/textfield.py +10 -4
  28. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/canvas.py +3 -0
  29. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/textfield.py +2 -2
  30. qpuiq-0.24/PUI/wx/base.py +246 -0
  31. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/canvas.py +2 -2
  32. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/combobox.py +14 -5
  33. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/layout.py +6 -0
  34. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/progressbar.py +2 -0
  35. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/scroll.py +13 -2
  36. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/textfield.py +15 -5
  37. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/window.py +6 -0
  38. {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/PKG-INFO +21 -14
  39. {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/SOURCES.txt +1 -0
  40. {qpuiq-0.10 → qpuiq-0.24}/README.md +20 -13
  41. qpuiq-0.10/PUI/PySide6/layout.py +0 -72
  42. qpuiq-0.10/PUI/PySide6/tree.py +0 -120
  43. qpuiq-0.10/PUI/textual/base.py +0 -113
  44. qpuiq-0.10/PUI/wx/base.py +0 -202
  45. {qpuiq-0.10 → qpuiq-0.24}/LICENSE.txt +0 -0
  46. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/__init__.py +0 -0
  47. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/application.py +0 -0
  48. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/button.py +0 -0
  49. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/checkbox.py +0 -0
  50. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/dialog.py +0 -0
  51. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/label.py +0 -0
  52. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/matplotlib.py +0 -0
  53. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/mdi.py +0 -0
  54. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/menu.py +0 -0
  55. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/modal.py +0 -0
  56. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/progressbar.py +0 -0
  57. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/radiobutton.py +0 -0
  58. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/splitter.py +0 -0
  59. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/tab.py +0 -0
  60. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/text.py +0 -0
  61. {qpuiq-0.10 → qpuiq-0.24}/PUI/PySide6/toolbar.py +0 -0
  62. {qpuiq-0.10 → qpuiq-0.24}/PUI/decorator.py +0 -0
  63. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/application.py +0 -0
  64. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/base.py +0 -0
  65. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/button.py +0 -0
  66. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/canvas.py +0 -0
  67. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/checkbox.py +0 -0
  68. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/label.py +0 -0
  69. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/layout.py +0 -0
  70. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/progressbar.py +0 -0
  71. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/radiobutton.py +0 -0
  72. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/scroll.py +0 -0
  73. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/tab.py +0 -0
  74. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/text.py +0 -0
  75. {qpuiq-0.10 → qpuiq-0.24}/PUI/flet/window.py +0 -0
  76. {qpuiq-0.10 → qpuiq-0.24}/PUI/interfaces.py +0 -0
  77. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/button.py +0 -0
  78. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/checkbox.py +0 -0
  79. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/progressbar.py +0 -0
  80. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/radiobutton.py +0 -0
  81. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/tab.py +0 -0
  82. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/text.py +0 -0
  83. {qpuiq-0.10 → qpuiq-0.24}/PUI/textual/window.py +0 -0
  84. {qpuiq-0.10 → qpuiq-0.24}/PUI/timeline.py +0 -0
  85. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/__init__.py +0 -0
  86. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/application.py +0 -0
  87. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/base.py +0 -0
  88. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/button.py +0 -0
  89. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/checkbox.py +0 -0
  90. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/label.py +0 -0
  91. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/layout.py +0 -0
  92. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/progressbar.py +0 -0
  93. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/radiobutton.py +0 -0
  94. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/scroll.py +0 -0
  95. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/tab.py +0 -0
  96. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/text.py +0 -0
  97. {qpuiq-0.10 → qpuiq-0.24}/PUI/tkinter/window.py +0 -0
  98. {qpuiq-0.10 → qpuiq-0.24}/PUI/utils.py +0 -0
  99. {qpuiq-0.10 → qpuiq-0.24}/PUI/view.py +0 -0
  100. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/__init__.py +0 -0
  101. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/application.py +0 -0
  102. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/button.py +0 -0
  103. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/checkbox.py +0 -0
  104. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/dialog.py +0 -0
  105. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/divider.py +0 -0
  106. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/label.py +0 -0
  107. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/radiobutton.py +0 -0
  108. {qpuiq-0.10 → qpuiq-0.24}/PUI/wx/text.py +0 -0
  109. {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/dependency_links.txt +0 -0
  110. {qpuiq-0.10 → qpuiq-0.24}/QPUIQ.egg-info/top_level.txt +0 -0
  111. {qpuiq-0.10 → qpuiq-0.24}/setup.cfg +0 -0
  112. {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.10
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
- # Components
194
- [Reference](REFERENCE.md)
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/kikit-ui
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(PUINode):
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.layout.insertItem(idx, child.outer)
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.layout.insertWidget(idx, child.outer, **params)
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.layout.removeItem(child.outer)
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 qt(self, **kwargs):
174
- for k,v in kwargs.items():
175
- self.qt_params[k] = v
176
- 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)
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.node = node
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.node._dblclicked(e)
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.node._mousedown(e)
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.node._mouseup(e)
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.node._mousemove(e)
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
- self.node._wheel(e)
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
- node = self.node.get_node()
53
- node.qpainter = QPainter()
54
- node.qpainter.begin(self)
55
- 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)
56
66
 
57
- if not node.style_bgcolor is None:
67
+ if not puinode.style_bgcolor is None:
58
68
  bgBrush = QtGui.QBrush()
59
- bgBrush.setColor(QtGui.QColor(node.style_bgcolor))
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
- node.qpainter.fillRect(rect, bgBrush)
63
-
64
- node.painter(node, *node.args)
65
-
66
- node.qpainter.end()
67
- 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
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.text for c in self.children].index(text)
36
+ index = [c.value for c in self.children].index(text)
34
37
  except:
35
- index = 0
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
@@ -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,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 = QtWidgets.QLabel()
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.curr_path.set(self.path):
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