qpuiq 0.13__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.13 → qpuiq-0.14}/PKG-INFO +12 -4
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/base.py +3 -3
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/canvas.py +38 -2
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/combobox.py +12 -2
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/image.py +20 -2
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/layout.py +8 -8
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/tree.py +25 -15
- {qpuiq-0.13 → qpuiq-0.14}/PUI/__init__.py +1 -1
- {qpuiq-0.13 → qpuiq-0.14}/PUI/common.py +6 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/__init__.py +1 -0
- qpuiq-0.14/PUI/textual/base.py +148 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/label.py +2 -2
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/layout.py +4 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/scroll.py +6 -4
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/canvas.py +3 -0
- qpuiq-0.14/PUI/wx/base.py +246 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/canvas.py +2 -2
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/combobox.py +13 -4
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/layout.py +6 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/scroll.py +13 -2
- {qpuiq-0.13 → qpuiq-0.14}/README.md +3 -2
- {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/PKG-INFO +12 -4
- {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/SOURCES.txt +0 -4
- qpuiq-0.13/PUI/textual/base.py +0 -113
- qpuiq-0.13/PUI/wx/base.py +0 -202
- {qpuiq-0.13 → qpuiq-0.14}/LICENSE.txt +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/__init__.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/application.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/button.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/checkbox.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/dialog.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/divider.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/label.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/matplotlib.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/mdi.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/menu.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/modal.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/progressbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/radiobutton.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/scroll.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/splitter.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/tab.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/table.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/text.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/textfield.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/toolbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/PySide6/window.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/decorator.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/dom.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/__init__.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/application.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/base.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/button.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/canvas.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/checkbox.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/label.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/layout.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/progressbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/radiobutton.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/scroll.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/tab.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/text.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/textfield.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/flet/window.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/interfaces.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/node.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/state.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/application.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/button.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/checkbox.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/progressbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/radiobutton.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/tab.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/text.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/textfield.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/textual/window.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/timeline.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/__init__.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/application.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/base.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/button.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/checkbox.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/label.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/layout.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/progressbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/radiobutton.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/scroll.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/tab.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/text.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/textfield.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/tkinter/window.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/utils.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/view.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/__init__.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/application.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/button.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/checkbox.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/dialog.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/divider.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/label.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/progressbar.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/radiobutton.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/text.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/textfield.py +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/PUI/wx/window.py +0 -0
- {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/dependency_links.txt +0 -0
- {qpuiq-0.13/QPUIQ.egg-info → qpuiq-0.14/qpuiq.egg-info}/top_level.txt +0 -0
- {qpuiq-0.13 → qpuiq-0.14}/setup.cfg +0 -0
- {qpuiq-0.13 → 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__()
|
|
@@ -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)
|
|
@@ -130,14 +132,14 @@ class QTreeNodeModelAdapter(QtCore.QAbstractItemModel):
|
|
|
130
132
|
return None
|
|
131
133
|
|
|
132
134
|
def rowCount(self, parent):
|
|
133
|
-
parent_node = parent.internalPointer() if parent.isValid() else self.
|
|
135
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.node
|
|
134
136
|
return len(parent_node.children)
|
|
135
137
|
|
|
136
138
|
def columnCount(self, parent):
|
|
137
139
|
return 1
|
|
138
140
|
|
|
139
141
|
def hasChildren(self, parent):
|
|
140
|
-
parent_node = parent.internalPointer() if parent.isValid() else self.
|
|
142
|
+
parent_node = parent.internalPointer() if parent.isValid() else self.node
|
|
141
143
|
return len(parent_node.children) > 0
|
|
142
144
|
|
|
143
145
|
def clicked(self, node):
|
|
@@ -167,15 +169,21 @@ class Tree(QtBaseWidget):
|
|
|
167
169
|
self.ui = prev.ui
|
|
168
170
|
self.qt_model = prev.qt_model
|
|
169
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()
|
|
170
177
|
else:
|
|
171
178
|
self.qt_model = None
|
|
172
179
|
self.curr_model = Prop()
|
|
173
180
|
self.ui = QtWidgets.QTreeView()
|
|
174
181
|
self.ui.setHeaderHidden(True)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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)
|
|
179
187
|
|
|
180
188
|
if self.model:
|
|
181
189
|
if self.curr_model.set(self.model):
|
|
@@ -186,11 +194,13 @@ class Tree(QtBaseWidget):
|
|
|
186
194
|
self.qt_model.modelReset.emit()
|
|
187
195
|
else:
|
|
188
196
|
if not self.qt_model:
|
|
189
|
-
self.qt_model = QTreeNodeModelAdapter(
|
|
197
|
+
self.qt_model = QTreeNodeModelAdapter()
|
|
190
198
|
self.qt_model.node = self
|
|
191
199
|
self.ui.setModel(self.qt_model)
|
|
192
200
|
else:
|
|
193
|
-
self.qt_model.
|
|
201
|
+
self.qt_model.beginResetModel()
|
|
202
|
+
self.qt_model.node = self
|
|
203
|
+
self.qt_model.endResetModel()
|
|
194
204
|
|
|
195
205
|
for pending in self.pendings:
|
|
196
206
|
pending[0](*pending[1:])
|
|
@@ -239,19 +249,19 @@ class Tree(QtBaseWidget):
|
|
|
239
249
|
|
|
240
250
|
def on_item_clicked(self, index):
|
|
241
251
|
treenode = index.internalPointer()
|
|
242
|
-
self.qt_model.clicked(treenode)
|
|
252
|
+
self.get_node().qt_model.clicked(treenode)
|
|
243
253
|
|
|
244
254
|
def on_item_double_clicked(self, index):
|
|
245
255
|
treenode = index.internalPointer()
|
|
246
|
-
self.qt_model.dblclicked(treenode)
|
|
256
|
+
self.get_node().qt_model.dblclicked(treenode)
|
|
247
257
|
|
|
248
258
|
def on_item_expanded(self, index):
|
|
249
259
|
treenode = index.internalPointer()
|
|
250
|
-
self.qt_model.expanded(treenode)
|
|
260
|
+
self.get_node().qt_model.expanded(treenode)
|
|
251
261
|
|
|
252
262
|
def on_item_collapsed(self, index):
|
|
253
263
|
treenode = index.internalPointer()
|
|
254
|
-
self.qt_model.collapsed(treenode)
|
|
264
|
+
self.get_node().qt_model.collapsed(treenode)
|
|
255
265
|
|
|
256
266
|
class TreeNode(PUINode):
|
|
257
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)
|