qpuiq 0.12__tar.gz → 0.14__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.12 → qpuiq-0.14}/PKG-INFO +12 -4
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/base.py +3 -3
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/canvas.py +38 -2
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/combobox.py +12 -2
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/image.py +20 -2
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/layout.py +8 -8
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/tree.py +35 -22
- {qpuiq-0.12 → qpuiq-0.14}/PUI/__init__.py +1 -1
- {qpuiq-0.12 → qpuiq-0.14}/PUI/common.py +6 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/__init__.py +1 -0
- qpuiq-0.14/PUI/textual/base.py +148 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/label.py +2 -2
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/layout.py +4 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/scroll.py +6 -4
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/canvas.py +3 -0
- qpuiq-0.14/PUI/wx/base.py +246 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/canvas.py +2 -2
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/combobox.py +13 -4
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/layout.py +6 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/scroll.py +13 -2
- {qpuiq-0.12 → qpuiq-0.14}/README.md +3 -2
- {qpuiq-0.12/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/PKG-INFO +12 -4
- {qpuiq-0.12/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/SOURCES.txt +0 -4
- qpuiq-0.12/PUI/textual/base.py +0 -113
- qpuiq-0.12/PUI/wx/base.py +0 -202
- {qpuiq-0.12 → qpuiq-0.14}/LICENSE.txt +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/__init__.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/application.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/button.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/checkbox.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/dialog.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/divider.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/label.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/matplotlib.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/mdi.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/menu.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/modal.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/progressbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/radiobutton.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/scroll.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/splitter.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/tab.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/table.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/text.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/textfield.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/toolbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/PySide6/window.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/decorator.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/dom.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/__init__.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/application.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/base.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/button.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/canvas.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/checkbox.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/label.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/layout.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/progressbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/radiobutton.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/scroll.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/tab.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/text.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/textfield.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/flet/window.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/interfaces.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/node.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/state.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/application.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/button.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/checkbox.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/progressbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/radiobutton.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/tab.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/text.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/textfield.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/textual/window.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/timeline.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/__init__.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/application.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/base.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/button.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/checkbox.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/label.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/layout.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/progressbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/radiobutton.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/scroll.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/tab.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/text.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/textfield.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/tkinter/window.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/utils.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/view.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/__init__.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/application.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/button.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/checkbox.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/dialog.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/divider.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/label.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/progressbar.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/radiobutton.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/text.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/textfield.py +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/PUI/wx/window.py +0 -0
- {qpuiq-0.12/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/dependency_links.txt +0 -0
- {qpuiq-0.12/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/top_level.txt +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/setup.cfg +0 -0
- {qpuiq-0.12 → qpuiq-0.14}/setup.py +0 -0
|
@@ -1,12 +1,19 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: qpuiq
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14
|
|
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
|
|
10
17
|
|
|
11
18
|
# What is PUI
|
|
12
19
|
PUI is a reactive/declarative UI framework with two-way data binding.
|
|
@@ -205,8 +212,9 @@ Then PUI will take care of view update [(code)](https://github.com/buganini/PUI/
|
|
|
205
212
|
* textual (Text Mode)
|
|
206
213
|
* no canvas
|
|
207
214
|
|
|
208
|
-
#
|
|
209
|
-
[Reference](REFERENCE.md)
|
|
215
|
+
# 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)
|
|
210
218
|
|
|
211
219
|
# Used by
|
|
212
220
|
* https://github.com/buganini/kikit-ui
|
|
@@ -193,20 +193,20 @@ class QtBaseLayout(PUINode):
|
|
|
193
193
|
from .modal import Modal
|
|
194
194
|
from .layout import Spacer
|
|
195
195
|
if isinstance(child, Spacer):
|
|
196
|
-
self.
|
|
196
|
+
self.qtlayout.insertItem(idx, child.outer)
|
|
197
197
|
elif isinstance(child, Modal):
|
|
198
198
|
pass
|
|
199
199
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
200
200
|
params = {}
|
|
201
201
|
if not child.layout_weight is None:
|
|
202
202
|
params["stretch"] = child.layout_weight
|
|
203
|
-
self.
|
|
203
|
+
self.qtlayout.insertWidget(idx, child.outer, **params)
|
|
204
204
|
|
|
205
205
|
def removeChild(self, idx, child):
|
|
206
206
|
from .modal import Modal
|
|
207
207
|
from .layout import Spacer
|
|
208
208
|
if isinstance(child, Spacer):
|
|
209
|
-
self.
|
|
209
|
+
self.qtlayout.removeItem(child.outer)
|
|
210
210
|
elif isinstance(child, Modal):
|
|
211
211
|
pass
|
|
212
212
|
elif isinstance(child, QtBaseWidget) or isinstance(child, QtBaseLayout):
|
|
@@ -2,8 +2,7 @@ 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):
|
|
@@ -46,6 +45,17 @@ 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()
|
|
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
|
|
49
59
|
self.node._wheel(e)
|
|
50
60
|
|
|
51
61
|
def paintEvent(self, event):
|
|
@@ -61,6 +71,8 @@ class PUIQtCanvas(QtWidgets.QWidget):
|
|
|
61
71
|
rect = QtCore.QRect(0, 0, self.width or self.geometry().width(), self.height or self.geometry().height())
|
|
62
72
|
node.qpainter.fillRect(rect, bgBrush)
|
|
63
73
|
|
|
74
|
+
node.width = self.geometry().width()
|
|
75
|
+
node.height = self.geometry().height()
|
|
64
76
|
node.painter(node, *node.args)
|
|
65
77
|
|
|
66
78
|
node.qpainter.end()
|
|
@@ -286,3 +298,27 @@ class Canvas(QtBaseWidget):
|
|
|
286
298
|
self.drawPolyline(shape.coords, color=stroke, width=width)
|
|
287
299
|
else:
|
|
288
300
|
raise RuntimeError(f"Not implemented: drawShapely({type(shape).__name__}) {dir(shape)}")
|
|
301
|
+
|
|
302
|
+
def loadImage(self, image_path):
|
|
303
|
+
return QImage(image_path)
|
|
304
|
+
|
|
305
|
+
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():
|
|
307
|
+
return
|
|
308
|
+
|
|
309
|
+
if src_width is None:
|
|
310
|
+
src_width = image.width() - src_x
|
|
311
|
+
if src_height is None:
|
|
312
|
+
src_height = image.height() - src_y
|
|
313
|
+
|
|
314
|
+
source_rect = QtCore.QRect(src_x, src_y, src_width, src_height)
|
|
315
|
+
|
|
316
|
+
if width is None:
|
|
317
|
+
width = src_width
|
|
318
|
+
if height is None:
|
|
319
|
+
height = src_height
|
|
320
|
+
|
|
321
|
+
dest_rect = QtCore.QRect(x, y, width, height)
|
|
322
|
+
self.qpainter.setOpacity(opacity)
|
|
323
|
+
self.qpainter.drawImage(dest_rect, image, source_rect)
|
|
324
|
+
self.qpainter.setOpacity(1.0)
|
|
@@ -24,13 +24,16 @@ class ComboBox(QtBaseWidget):
|
|
|
24
24
|
super().update(prev)
|
|
25
25
|
|
|
26
26
|
def postSync(self):
|
|
27
|
+
index = 0
|
|
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
38
|
index = 0
|
|
36
39
|
|
|
@@ -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)
|
|
@@ -5,24 +5,24 @@ class HBox(QtBaseLayout):
|
|
|
5
5
|
def update(self, prev):
|
|
6
6
|
if prev and prev.ui:
|
|
7
7
|
self.ui = prev.ui
|
|
8
|
-
self.
|
|
8
|
+
self.qtlayout = prev.qtlayout
|
|
9
9
|
else:
|
|
10
10
|
self.ui = QtWidgets.QWidget()
|
|
11
|
-
self.
|
|
12
|
-
self.
|
|
13
|
-
self.ui.setLayout(self.
|
|
11
|
+
self.qtlayout = QtWidgets.QHBoxLayout()
|
|
12
|
+
self.qtlayout.setContentsMargins(0,0,0,0)
|
|
13
|
+
self.ui.setLayout(self.qtlayout)
|
|
14
14
|
super().update(prev)
|
|
15
15
|
|
|
16
16
|
class VBox(QtBaseLayout):
|
|
17
17
|
def update(self, prev):
|
|
18
18
|
if prev and prev.ui:
|
|
19
19
|
self.ui = prev.ui
|
|
20
|
-
self.
|
|
20
|
+
self.qtlayout = prev.qtlayout
|
|
21
21
|
else:
|
|
22
22
|
self.ui = QtWidgets.QWidget()
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.ui.setLayout(self.
|
|
23
|
+
self.qtlayout = QtWidgets.QVBoxLayout()
|
|
24
|
+
self.qtlayout.setContentsMargins(0,0,0,0)
|
|
25
|
+
self.ui.setLayout(self.qtlayout)
|
|
26
26
|
super().update(prev)
|
|
27
27
|
|
|
28
28
|
class Spacer(PUINode):
|
|
@@ -2,6 +2,9 @@ from .. import *
|
|
|
2
2
|
from .base import *
|
|
3
3
|
from PySide6.QtCore import Qt, QModelIndex, QAbstractItemModel
|
|
4
4
|
|
|
5
|
+
# XXX
|
|
6
|
+
# If click handler triggers a model reset, dblclick handler will not be called
|
|
7
|
+
|
|
5
8
|
class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
|
|
6
9
|
def __init__(self, model: "BaseTreeAdapter"):
|
|
7
10
|
super().__init__()
|
|
@@ -21,7 +24,7 @@ class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
|
|
|
21
24
|
|
|
22
25
|
defaultFlags = super().flags(index)
|
|
23
26
|
|
|
24
|
-
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
|
|
27
|
+
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
|
|
25
28
|
|
|
26
29
|
def canDropMimeData(self, data, action, row, column, parent):
|
|
27
30
|
if parent.isValid():
|
|
@@ -77,13 +80,12 @@ class QAbstractItemModelAdapter(QtCore.QAbstractItemModel):
|
|
|
77
80
|
self.model.collapsed(node)
|
|
78
81
|
|
|
79
82
|
class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
80
|
-
def __init__(self
|
|
83
|
+
def __init__(self):
|
|
81
84
|
super().__init__()
|
|
82
85
|
self.node = None
|
|
83
|
-
self.rootnode = rootnode
|
|
84
86
|
|
|
85
87
|
def index(self, row, column, parent = QtCore.QModelIndex()):
|
|
86
|
-
parent_node = parent.internalPointer() if parent.isValid() else self.
|
|
88
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.node
|
|
87
89
|
if 0 <= row and row < len(parent_node.children):
|
|
88
90
|
child = parent_node.children[row]
|
|
89
91
|
return self.createIndex(row, column, child)
|
|
@@ -95,7 +97,7 @@ class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
|
95
97
|
|
|
96
98
|
defaultFlags = super().flags(index)
|
|
97
99
|
|
|
98
|
-
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
|
|
100
|
+
return defaultFlags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
|
|
99
101
|
|
|
100
102
|
def canDropMimeData(self, data, action, row, column, parent):
|
|
101
103
|
if parent.isValid():
|
|
@@ -115,6 +117,8 @@ class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
|
115
117
|
return QModelIndex()
|
|
116
118
|
node = index.internalPointer()
|
|
117
119
|
parent_node = node.parent
|
|
120
|
+
if not isinstance(parent_node, TreeNode):
|
|
121
|
+
parent_node = None
|
|
118
122
|
if parent_node:
|
|
119
123
|
return self.createIndex(0, 0, parent_node)
|
|
120
124
|
return QModelIndex()
|
|
@@ -128,14 +132,14 @@ class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
|
128
132
|
return None
|
|
129
133
|
|
|
130
134
|
def rowCount(self, parent):
|
|
131
|
-
parent_node = parent.internalPointer() if parent.isValid() else self.
|
|
135
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.node
|
|
132
136
|
return len(parent_node.children)
|
|
133
137
|
|
|
134
138
|
def columnCount(self, parent):
|
|
135
139
|
return 1
|
|
136
140
|
|
|
137
141
|
def hasChildren(self, parent):
|
|
138
|
-
parent_node = parent.internalPointer() if parent.isValid() else self.
|
|
142
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.node
|
|
139
143
|
return len(parent_node.children) > 0
|
|
140
144
|
|
|
141
145
|
def clicked(self, node):
|
|
@@ -157,21 +161,29 @@ class Tree(QtBaseWidget):
|
|
|
157
161
|
self.model = model
|
|
158
162
|
self.curr_model = None
|
|
159
163
|
self.pendings = []
|
|
164
|
+
self._expand_callback = None
|
|
165
|
+
self._collapse_callback = None
|
|
160
166
|
|
|
161
167
|
def update(self, prev):
|
|
162
168
|
if prev and prev.ui:
|
|
163
169
|
self.ui = prev.ui
|
|
164
170
|
self.qt_model = prev.qt_model
|
|
165
171
|
self.curr_model = prev.curr_model
|
|
172
|
+
|
|
173
|
+
self.ui.clicked.disconnect()
|
|
174
|
+
self.ui.doubleClicked.disconnect()
|
|
175
|
+
self.ui.expanded.disconnect()
|
|
176
|
+
self.ui.collapsed.disconnect()
|
|
166
177
|
else:
|
|
167
178
|
self.qt_model = None
|
|
168
179
|
self.curr_model = Prop()
|
|
169
180
|
self.ui = QtWidgets.QTreeView()
|
|
170
181
|
self.ui.setHeaderHidden(True)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
|
|
183
|
+
self.ui.clicked.connect(self.on_item_clicked)
|
|
184
|
+
self.ui.doubleClicked.connect(self.on_item_double_clicked)
|
|
185
|
+
self.ui.expanded.connect(self.on_item_expanded)
|
|
186
|
+
self.ui.collapsed.connect(self.on_item_collapsed)
|
|
175
187
|
|
|
176
188
|
if self.model:
|
|
177
189
|
if self.curr_model.set(self.model):
|
|
@@ -181,9 +193,14 @@ class Tree(QtBaseWidget):
|
|
|
181
193
|
else:
|
|
182
194
|
self.qt_model.modelReset.emit()
|
|
183
195
|
else:
|
|
184
|
-
self.qt_model
|
|
185
|
-
|
|
186
|
-
|
|
196
|
+
if not self.qt_model:
|
|
197
|
+
self.qt_model = QTreeNodeModelAdapter()
|
|
198
|
+
self.qt_model.node = self
|
|
199
|
+
self.ui.setModel(self.qt_model)
|
|
200
|
+
else:
|
|
201
|
+
self.qt_model.beginResetModel()
|
|
202
|
+
self.qt_model.node = self
|
|
203
|
+
self.qt_model.endResetModel()
|
|
187
204
|
|
|
188
205
|
for pending in self.pendings:
|
|
189
206
|
pending[0](*pending[1:])
|
|
@@ -231,24 +248,20 @@ class Tree(QtBaseWidget):
|
|
|
231
248
|
cb(*args, **kwargs)
|
|
232
249
|
|
|
233
250
|
def on_item_clicked(self, index):
|
|
234
|
-
node = self.get_node()
|
|
235
251
|
treenode = index.internalPointer()
|
|
236
|
-
self.qt_model.clicked(treenode)
|
|
252
|
+
self.get_node().qt_model.clicked(treenode)
|
|
237
253
|
|
|
238
254
|
def on_item_double_clicked(self, index):
|
|
239
|
-
node = self.get_node()
|
|
240
255
|
treenode = index.internalPointer()
|
|
241
|
-
self.qt_model.dblclicked(treenode)
|
|
256
|
+
self.get_node().qt_model.dblclicked(treenode)
|
|
242
257
|
|
|
243
258
|
def on_item_expanded(self, index):
|
|
244
|
-
node = self.get_node()
|
|
245
259
|
treenode = index.internalPointer()
|
|
246
|
-
self.qt_model.expanded(treenode)
|
|
260
|
+
self.get_node().qt_model.expanded(treenode)
|
|
247
261
|
|
|
248
262
|
def on_item_collapsed(self, index):
|
|
249
|
-
node = self.get_node()
|
|
250
263
|
treenode = index.internalPointer()
|
|
251
|
-
self.qt_model.collapsed(treenode)
|
|
264
|
+
self.get_node().qt_model.collapsed(treenode)
|
|
252
265
|
|
|
253
266
|
class TreeNode(PUINode):
|
|
254
267
|
def __init__(self, data=""):
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from .. import *
|
|
2
|
+
from textual import widgets, containers
|
|
3
|
+
|
|
4
|
+
class TBase(PUINode):
|
|
5
|
+
scroll = False
|
|
6
|
+
container_x = False # axis
|
|
7
|
+
container_y = False # axis
|
|
8
|
+
expand_x_prio = 0
|
|
9
|
+
expand_y_prio = 0
|
|
10
|
+
expand_x1_children = 0
|
|
11
|
+
expand_x2_children = 0
|
|
12
|
+
expand_x3_children = 0
|
|
13
|
+
expand_x4_children = 0
|
|
14
|
+
expand_y1_children = 0
|
|
15
|
+
expand_y2_children = 0
|
|
16
|
+
expand_y3_children = 0
|
|
17
|
+
expand_y4_children = 0
|
|
18
|
+
cached_tparent = None
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def expand_x(self):
|
|
22
|
+
parent = self.cached_tparent
|
|
23
|
+
expand = self.expand_x_prio
|
|
24
|
+
if not parent:
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
# textual handles 1fr as shrinkable, but we need scrolller's content not to shrink
|
|
28
|
+
# See Exp.1 in refs/textual_layout.py
|
|
29
|
+
if parent.scroll:
|
|
30
|
+
return False
|
|
31
|
+
|
|
32
|
+
# textual populates auto(1fr) to be 1fr(1fr), but we require expanding not to go over the container
|
|
33
|
+
# See Exp.2 in refs/textual_layout.py
|
|
34
|
+
if not parent.expand_x and expand < 3:
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
return expand
|
|
38
|
+
|
|
39
|
+
@property
|
|
40
|
+
def expand_y(self):
|
|
41
|
+
parent = self.cached_tparent
|
|
42
|
+
expand = self.expand_y_prio
|
|
43
|
+
if not parent:
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
# textual handles 1fr as shrinkable, but we need scrolller's content not to shrink
|
|
47
|
+
# See Exp.1 in refs/textual_layout.py
|
|
48
|
+
if parent.scroll:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
# textual populates auto(1fr) to be 1fr(1fr), but we require expanding not to go over the container
|
|
52
|
+
# See Exp.2 in refs/textual_layout.py
|
|
53
|
+
if not parent.expand_y and expand < 3:
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
return expand
|
|
57
|
+
|
|
58
|
+
def tremove(self):
|
|
59
|
+
self.ui.remove()
|
|
60
|
+
|
|
61
|
+
def destroy(self, direct):
|
|
62
|
+
self.ui.remove()
|
|
63
|
+
return super().destroy(direct)
|
|
64
|
+
|
|
65
|
+
def update(self, prev):
|
|
66
|
+
super().update(prev)
|
|
67
|
+
|
|
68
|
+
self.cached_tparent = parent = self.tparent
|
|
69
|
+
if parent:
|
|
70
|
+
if self.layout_weight:
|
|
71
|
+
if parent.container_x:
|
|
72
|
+
self.expand_x_prio = 4
|
|
73
|
+
if parent.container_y:
|
|
74
|
+
self.expand_y_prio = 4
|
|
75
|
+
|
|
76
|
+
if self.expand_x_prio >= 1:
|
|
77
|
+
parent.expand_x1_children += 1
|
|
78
|
+
if self.expand_x_prio >= 2:
|
|
79
|
+
parent.expand_x2_children += 1
|
|
80
|
+
if self.expand_x_prio >= 3:
|
|
81
|
+
parent.expand_x3_children += 1
|
|
82
|
+
if self.expand_x_prio >= 4:
|
|
83
|
+
parent.expand_x4_children += 1
|
|
84
|
+
|
|
85
|
+
if self.expand_y_prio >= 1:
|
|
86
|
+
parent.expand_y1_children += 1
|
|
87
|
+
if self.expand_y_prio >= 2:
|
|
88
|
+
parent.expand_y2_children += 1
|
|
89
|
+
if self.expand_y_prio >= 3:
|
|
90
|
+
parent.expand_y3_children += 1
|
|
91
|
+
if self.expand_y_prio >= 4:
|
|
92
|
+
parent.expand_y4_children += 1
|
|
93
|
+
|
|
94
|
+
def postUpdate(self):
|
|
95
|
+
super().postUpdate()
|
|
96
|
+
parent = self.cached_tparent
|
|
97
|
+
if parent:
|
|
98
|
+
if parent.container_x:
|
|
99
|
+
if self.expand_x_prio < 1 and parent.expand_x1_children > 0:
|
|
100
|
+
self.expand_x_prio = 0
|
|
101
|
+
if self.expand_x_prio < 2 and parent.expand_x2_children > 0:
|
|
102
|
+
self.expand_x_prio = 0
|
|
103
|
+
if self.expand_x_prio < 3 and parent.expand_x3_children > 0:
|
|
104
|
+
self.expand_x_prio = 0
|
|
105
|
+
if self.expand_x_prio < 4 and parent.expand_x4_children > 0:
|
|
106
|
+
self.expand_x_prio = 0
|
|
107
|
+
|
|
108
|
+
if parent.container_y:
|
|
109
|
+
if self.expand_y_prio < 1 and parent.expand_y1_children > 0:
|
|
110
|
+
self.expand_y_prio = 0
|
|
111
|
+
if self.expand_y_prio < 2 and parent.expand_y2_children > 0:
|
|
112
|
+
self.expand_y_prio = 0
|
|
113
|
+
if self.expand_y_prio < 3 and parent.expand_y3_children > 0:
|
|
114
|
+
self.expand_y_prio = 0
|
|
115
|
+
if self.expand_y_prio < 4 and parent.expand_y4_children > 0:
|
|
116
|
+
self.expand_y_prio = 0
|
|
117
|
+
|
|
118
|
+
self.t_update_layout()
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def tparent(self):
|
|
122
|
+
parent = self.parent
|
|
123
|
+
while not isinstance(parent, TBase):
|
|
124
|
+
if parent==parent.parent:
|
|
125
|
+
parent = None
|
|
126
|
+
break
|
|
127
|
+
parent = parent.parent
|
|
128
|
+
return parent
|
|
129
|
+
|
|
130
|
+
def t_update_layout(self):
|
|
131
|
+
if not self.ui:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
width = "auto"
|
|
135
|
+
if self.expand_x:
|
|
136
|
+
width = "1fr"
|
|
137
|
+
|
|
138
|
+
height = "auto"
|
|
139
|
+
if self.expand_y:
|
|
140
|
+
height = "1fr"
|
|
141
|
+
|
|
142
|
+
if self._debug:
|
|
143
|
+
print("layout", self.key, f"{width}:{height} expand_x={self.expand_x}", f"expand_y={self.expand_y}", f"expand_x_prio={self.expand_x_prio}", f"expand_y_prio={self.expand_y_prio}")
|
|
144
|
+
self.ui.styles.width = width
|
|
145
|
+
self.ui.styles.height = height
|
|
146
|
+
|
|
147
|
+
class TPUIView(PUIView):
|
|
148
|
+
pui_virtual = True
|
|
@@ -13,13 +13,12 @@ class Label(TBase):
|
|
|
13
13
|
self.widget = prev.widget
|
|
14
14
|
else:
|
|
15
15
|
self.ui = containers.Container()
|
|
16
|
-
self.ui.set_styles("width: auto; height: auto;")
|
|
17
16
|
if self._onClicked:
|
|
18
17
|
if self.widget is None or not isinstance(self.widget, widgets.Button):
|
|
19
18
|
if self.widget:
|
|
20
19
|
self.widget.remove()
|
|
21
20
|
self.widget = widgets.Button(self.text)
|
|
22
|
-
self.widget.set_styles("
|
|
21
|
+
self.widget.set_styles("border-top: none; border-bottom: none;")
|
|
23
22
|
self.widget.puinode = self
|
|
24
23
|
else:
|
|
25
24
|
self.widget.label = self.text
|
|
@@ -33,4 +32,5 @@ class Label(TBase):
|
|
|
33
32
|
super().update(prev)
|
|
34
33
|
|
|
35
34
|
def postUpdate(self):
|
|
35
|
+
super().postUpdate()
|
|
36
36
|
self.ui.mount(self.widget)
|
|
@@ -3,6 +3,8 @@ from .base import *
|
|
|
3
3
|
|
|
4
4
|
class VBox(TBase):
|
|
5
5
|
container_y = True
|
|
6
|
+
expand_x_prio = 1
|
|
7
|
+
expand_y_prio = 2
|
|
6
8
|
def update(self, prev):
|
|
7
9
|
if prev and prev.ui:
|
|
8
10
|
self.ui = prev.ui
|
|
@@ -20,6 +22,8 @@ class VBox(TBase):
|
|
|
20
22
|
|
|
21
23
|
class HBox(TBase):
|
|
22
24
|
container_x = True
|
|
25
|
+
expand_x_prio = 2
|
|
26
|
+
expand_y_prio = 1
|
|
23
27
|
def update(self, prev):
|
|
24
28
|
if prev and prev.ui:
|
|
25
29
|
self.ui = prev.ui
|
|
@@ -4,8 +4,6 @@ import math
|
|
|
4
4
|
|
|
5
5
|
class Scroll(TBase):
|
|
6
6
|
END = -0.0
|
|
7
|
-
weak_expand_x = True
|
|
8
|
-
weak_expand_y = True
|
|
9
7
|
scroll = True
|
|
10
8
|
def __init__(self, vertical=None, horizontal=False):
|
|
11
9
|
self.vertical = vertical
|
|
@@ -19,23 +17,27 @@ class Scroll(TBase):
|
|
|
19
17
|
self.ui = prev.ui
|
|
20
18
|
else:
|
|
21
19
|
self.ui = containers.ScrollableContainer()
|
|
20
|
+
|
|
22
21
|
v = "auto"
|
|
23
22
|
self.container_y = True
|
|
23
|
+
self.expand_y_prio = 3
|
|
24
24
|
if self.vertical is True:
|
|
25
25
|
v = "scroll"
|
|
26
26
|
elif self.vertical is False:
|
|
27
27
|
v = "hidden"
|
|
28
28
|
self.container_y = False
|
|
29
|
-
self.
|
|
29
|
+
self.expand_y_prio = 1
|
|
30
30
|
|
|
31
31
|
h = "auto"
|
|
32
32
|
self.container_x = True
|
|
33
|
+
self.expand_x_prio = 3
|
|
33
34
|
if self.horizontal is True:
|
|
34
35
|
h = "scroll"
|
|
35
36
|
elif self.horizontal is False:
|
|
36
37
|
h = "hidden"
|
|
37
38
|
self.container_x = False
|
|
38
|
-
self.
|
|
39
|
+
self.expand_x_prio = 1
|
|
40
|
+
|
|
39
41
|
self.ui.set_styles(f"overflow-x: {h}; overflow-y: {v};")
|
|
40
42
|
|
|
41
43
|
super().update(prev)
|