qpuiq 0.23__py3-none-any.whl

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 (104) hide show
  1. PUI/PySide6/__init__.py +49 -0
  2. PUI/PySide6/application.py +58 -0
  3. PUI/PySide6/base.py +285 -0
  4. PUI/PySide6/button.py +21 -0
  5. PUI/PySide6/canvas.py +345 -0
  6. PUI/PySide6/checkbox.py +32 -0
  7. PUI/PySide6/combobox.py +85 -0
  8. PUI/PySide6/dialog.py +72 -0
  9. PUI/PySide6/divider.py +23 -0
  10. PUI/PySide6/image.py +48 -0
  11. PUI/PySide6/label.py +33 -0
  12. PUI/PySide6/layout.py +141 -0
  13. PUI/PySide6/matplotlib.py +23 -0
  14. PUI/PySide6/mdi.py +33 -0
  15. PUI/PySide6/menu.py +85 -0
  16. PUI/PySide6/modal.py +132 -0
  17. PUI/PySide6/progressbar.py +17 -0
  18. PUI/PySide6/radiobutton.py +29 -0
  19. PUI/PySide6/scroll.py +155 -0
  20. PUI/PySide6/splitter.py +25 -0
  21. PUI/PySide6/tab.py +39 -0
  22. PUI/PySide6/table.py +147 -0
  23. PUI/PySide6/text.py +35 -0
  24. PUI/PySide6/textfield.py +62 -0
  25. PUI/PySide6/toolbar.py +57 -0
  26. PUI/PySide6/tree.py +290 -0
  27. PUI/PySide6/window.py +82 -0
  28. PUI/__init__.py +46 -0
  29. PUI/common.py +26 -0
  30. PUI/decorator.py +20 -0
  31. PUI/dom.py +263 -0
  32. PUI/flet/__init__.py +22 -0
  33. PUI/flet/application.py +42 -0
  34. PUI/flet/base.py +37 -0
  35. PUI/flet/button.py +20 -0
  36. PUI/flet/canvas.py +86 -0
  37. PUI/flet/checkbox.py +23 -0
  38. PUI/flet/divider.py +14 -0
  39. PUI/flet/label.py +27 -0
  40. PUI/flet/layout.py +50 -0
  41. PUI/flet/progressbar.py +21 -0
  42. PUI/flet/radiobutton.py +27 -0
  43. PUI/flet/scroll.py +83 -0
  44. PUI/flet/tab.py +42 -0
  45. PUI/flet/text.py +55 -0
  46. PUI/flet/textfield.py +58 -0
  47. PUI/flet/window.py +25 -0
  48. PUI/interfaces.py +97 -0
  49. PUI/node.py +432 -0
  50. PUI/state.py +711 -0
  51. PUI/textual/__init__.py +35 -0
  52. PUI/textual/application.py +82 -0
  53. PUI/textual/base.py +148 -0
  54. PUI/textual/button.py +17 -0
  55. PUI/textual/checkbox.py +21 -0
  56. PUI/textual/label.py +36 -0
  57. PUI/textual/layout.py +52 -0
  58. PUI/textual/progressbar.py +17 -0
  59. PUI/textual/radiobutton.py +24 -0
  60. PUI/textual/scroll.py +74 -0
  61. PUI/textual/tab.py +75 -0
  62. PUI/textual/text.py +32 -0
  63. PUI/textual/textfield.py +55 -0
  64. PUI/textual/window.py +7 -0
  65. PUI/timeline.py +36 -0
  66. PUI/tkinter/__init__.py +43 -0
  67. PUI/tkinter/application.py +49 -0
  68. PUI/tkinter/base.py +68 -0
  69. PUI/tkinter/button.py +15 -0
  70. PUI/tkinter/canvas.py +52 -0
  71. PUI/tkinter/checkbox.py +27 -0
  72. PUI/tkinter/label.py +17 -0
  73. PUI/tkinter/layout.py +114 -0
  74. PUI/tkinter/progressbar.py +17 -0
  75. PUI/tkinter/radiobutton.py +26 -0
  76. PUI/tkinter/scroll.py +201 -0
  77. PUI/tkinter/tab.py +52 -0
  78. PUI/tkinter/text.py +20 -0
  79. PUI/tkinter/textfield.py +53 -0
  80. PUI/tkinter/window.py +51 -0
  81. PUI/utils.py +15 -0
  82. PUI/view.py +161 -0
  83. PUI/wx/__init__.py +19 -0
  84. PUI/wx/application.py +44 -0
  85. PUI/wx/base.py +246 -0
  86. PUI/wx/button.py +16 -0
  87. PUI/wx/canvas.py +255 -0
  88. PUI/wx/checkbox.py +25 -0
  89. PUI/wx/combobox.py +81 -0
  90. PUI/wx/dialog.py +66 -0
  91. PUI/wx/divider.py +19 -0
  92. PUI/wx/label.py +18 -0
  93. PUI/wx/layout.py +52 -0
  94. PUI/wx/progressbar.py +19 -0
  95. PUI/wx/radiobutton.py +27 -0
  96. PUI/wx/scroll.py +55 -0
  97. PUI/wx/text.py +23 -0
  98. PUI/wx/textfield.py +66 -0
  99. PUI/wx/window.py +64 -0
  100. qpuiq-0.23.dist-info/LICENSE.txt +21 -0
  101. qpuiq-0.23.dist-info/METADATA +234 -0
  102. qpuiq-0.23.dist-info/RECORD +104 -0
  103. qpuiq-0.23.dist-info/WHEEL +5 -0
  104. qpuiq-0.23.dist-info/top_level.txt +1 -0
PUI/timeline.py ADDED
@@ -0,0 +1,36 @@
1
+ from threading import Timer
2
+ from .view import *
3
+
4
+ class TimelineView(PUINode):
5
+ pui_virtual = True
6
+ def __init__(self, ttl_sec):
7
+ super().__init__()
8
+ self.timer = None
9
+ self.ttl_sec = ttl_sec
10
+
11
+ def update(self, prev):
12
+ if prev and hasattr(prev, "timer"):
13
+ self.timer = prev.timer
14
+ else:
15
+ self.timer = Timer(self.ttl_sec, self.timer_cb)
16
+ self.timer.setDaemon(True)
17
+ self.timer.start()
18
+
19
+ def timer_cb(self):
20
+ if not self.timer:
21
+ return
22
+ node = self.get_node()
23
+ root = node.root
24
+ if not root:
25
+ return
26
+ root.redraw()
27
+ node.timer = Timer(self.ttl_sec, self.timer_cb)
28
+ node.timer.setDaemon(True)
29
+ node.timer.start()
30
+
31
+ def destroy(self, direct):
32
+ timer = self.timer
33
+ self.timer = None
34
+ if timer:
35
+ timer.cancel()
36
+ super().destroy(direct)
@@ -0,0 +1,43 @@
1
+ from .application import *
2
+ from .button import *
3
+ from .canvas import *
4
+ from .checkbox import *
5
+ from .label import *
6
+ from .layout import *
7
+ from .progressbar import *
8
+ from .radiobutton import *
9
+ from .scroll import *
10
+ from .tab import *
11
+ from .text import *
12
+ from .textfield import *
13
+ from .window import *
14
+
15
+ PUIView = TPUIView
16
+
17
+ def PUI(func):
18
+ """
19
+ PUI.tkinter.PUI triggers update() by .after()
20
+ """
21
+ def func_wrapper(*args, **kwargs):
22
+ class PUIViewWrapper(TPUIView):
23
+ pui_virtual = True
24
+ def __init__(self, name):
25
+ self.name = name
26
+ super().__init__()
27
+
28
+ def content(self):
29
+ return func(*args, **kwargs)
30
+
31
+ ret = PUIViewWrapper(func.__name__)
32
+ return ret
33
+
34
+ return func_wrapper
35
+ class DummyWidget(PUINode):
36
+ pui_supported = False
37
+ def __init__(self, *args, **kwargs):
38
+ super().__init__()
39
+
40
+ Combobox = DummyWidget
41
+ ComboboxItem = DummyWidget
42
+
43
+ PUI_BACKEND = "tkinter"
@@ -0,0 +1,49 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class Application(TPUIView):
5
+ def __init__(self, *args, **kwargs):
6
+ super().__init__(*args, **kwargs)
7
+ self.theme = None
8
+
9
+ def tkinter(self, theme=None, **kwargs):
10
+ super().tkinter(**kwargs)
11
+ self.theme = theme
12
+ return self
13
+
14
+ def update(self, prev=None):
15
+ if prev and prev.ui:
16
+ self.ui = prev.ui
17
+ else:
18
+ self.ui = tk.Tk()
19
+ ttkStyle = ttk.Style(self.ui)
20
+ if self.theme:
21
+ ttkStyle.theme_use(self.theme)
22
+ self.ui.withdraw()
23
+
24
+ super().update(prev)
25
+
26
+ def addChild(self, idx, child):
27
+ pass
28
+
29
+ def removeChild(self, idx, child):
30
+ pass
31
+
32
+ def start(self):
33
+ self.ui.mainloop()
34
+
35
+
36
+ def PUIApp(func):
37
+ def func_wrapper(*args, **kwargs):
38
+ class PUIAppWrapper(Application):
39
+ def __init__(self, name):
40
+ self.name = name
41
+ super().__init__()
42
+
43
+ def content(self):
44
+ return func(*args, **kwargs)
45
+
46
+ ret = PUIAppWrapper(func.__name__)
47
+ return ret
48
+
49
+ return func_wrapper
PUI/tkinter/base.py ADDED
@@ -0,0 +1,68 @@
1
+ from .. import *
2
+ import tkinter as tk
3
+ from tkinter import ttk
4
+ from tkinter import font as tkFont
5
+ import functools
6
+
7
+ class TPUIView(PUIView):
8
+ pui_virtual = True
9
+ def redraw(self):
10
+ if self.ui:
11
+ self.ui.after(0, self.sync)
12
+ else:
13
+ self.sync()
14
+
15
+ class TkBaseWidget(PUINode):
16
+ use_ttk = False
17
+ pui_terminal = True
18
+ def __init__(self, layout=None, side=None):
19
+ super().__init__()
20
+ self.layout_type = layout
21
+ self.side = side
22
+
23
+ @property
24
+ def tkparent(self):
25
+ parent = self.parent
26
+ while not isinstance(parent, TkBaseWidget) and not isinstance(parent, TPUIView):
27
+ parent = parent.parent
28
+ if parent==parent.parent:
29
+ return None
30
+ return parent
31
+
32
+ def destroy(self, direct):
33
+ if self.ui:
34
+ self.ui.destroy() # tk's destroy
35
+ self.ui = None
36
+
37
+ def update(self, prev):
38
+ if not self.layout_width is None:
39
+ self.ui.configure(width=self.layout_width)
40
+ if not self.layout_height is None:
41
+ self.ui.configure(height=self.layout_height)
42
+
43
+ if self.use_ttk:
44
+ styleKey = f"S{id(self.ui)}.{self.use_ttk}"
45
+ tkstyle = ttk.Style()
46
+ if not self.style_color is None:
47
+ tkstyle.configure(styleKey, foreground=f"#{self.style_color:06X}")
48
+ if not self.style_bgcolor is None:
49
+ tkstyle.configure(styleKey, background=f"#{self.style_bgcolor:06X}")
50
+ if self.style_fontfamily or self.style_fontsize:
51
+ default = tkFont.nametofont('TkDefaultFont').actual()
52
+ font_family = self.style_fontfamily or default["family"]
53
+ font_size = self.style_fontsize or default["size"]
54
+ font_weight = self.style_fontweight or default["weight"]
55
+ tkstyle.configure(styleKey, font=(font_family, font_size, font_weight))
56
+ self.ui.configure(style=styleKey)
57
+ else:
58
+ if not self.style_color is None:
59
+ self.ui.configure(fg=f"#{self.style_color:06X}")
60
+ if not self.style_bgcolor is None:
61
+ self.ui.configure(bg=f"#{self.style_bgcolor:06X}")
62
+ if self.style_fontfamily or self.style_fontsize:
63
+ default = tkFont.nametofont('TkDefaultFont').actual()
64
+ font_family = self.style_fontfamily or default["family"]
65
+ font_size = self.style_fontsize or default["size"]
66
+ font_weight = self.style_fontweight or default["weight"]
67
+ self.ui.configure(font=(font_family, font_size, font_weight))
68
+ super().update(prev)
PUI/tkinter/button.py ADDED
@@ -0,0 +1,15 @@
1
+ from .. import *
2
+ from .base import *
3
+ class Button(TkBaseWidget):
4
+ def __init__(self, text):
5
+ super().__init__()
6
+ self.text = text
7
+
8
+ def update(self, prev):
9
+ if prev and prev.ui:
10
+ self.ui = prev.ui
11
+ self.ui.config(text = self.text)
12
+ self.ui.config(command = self._clicked)
13
+ else:
14
+ self.ui = tk.Button(self.tkparent.inner, text=self.text, command=self._clicked)
15
+ super().update(prev)
PUI/tkinter/canvas.py ADDED
@@ -0,0 +1,52 @@
1
+ from .. import *
2
+ from .base import *
3
+ import itertools
4
+ class Canvas(TkBaseWidget):
5
+ pui_terminal = True
6
+ expand_x_prio = 2
7
+ expand_y_prio = 2
8
+
9
+ def __init__(self, painter, *args):
10
+ super().__init__()
11
+ self.painter = painter
12
+ self.args = args
13
+
14
+ def update(self, prev):
15
+ if prev and prev.ui:
16
+ self.ui = prev.ui
17
+ else:
18
+ self.ui = tk.Canvas(self.tkparent.inner)
19
+ self.ui.delete("all")
20
+
21
+ self.painter(self, *self.args)
22
+ super().update(prev)
23
+
24
+ def drawText(self, x, y, text, w=None, h=None, rotate=0, anchor=Anchor.LEFT_TOP):
25
+ if rotate !=0:
26
+ print("drawText: rotate not implemented")
27
+ self.ui.create_text(x, y, text=text)
28
+
29
+ def drawLine(self, x1, y1, x2, y2, color=None, width=None):
30
+ params = {}
31
+ if not color is None:
32
+ params["fill"] = f"#{color:06X}"
33
+ if not width is None:
34
+ params["width"] = width
35
+ self.ui.create_line(x1, y1, x2, y2, **params)
36
+
37
+ def drawPolyline(self, coords, color=None, width=None):
38
+ params = {}
39
+ if not color is None:
40
+ params["fill"] = f"#{color:06X}"
41
+ if not width is None:
42
+ params["width"] = width
43
+ self.ui.create_line(*itertools.chain(*coords), **params)
44
+
45
+ def drawPolygon(self, coords, fill=None, stroke=None, width=1):
46
+ print("drawPolygon not implemented")
47
+
48
+ def drawRect(self, x1, y1, x2, y2, fill=None, stroke=None, width=1):
49
+ print("drawRect not implemented")
50
+
51
+ def drawEllipse(self, x, y, rx, ry, fill=None, stroke=None, width=1):
52
+ print("drawEllipse not implemented")
@@ -0,0 +1,27 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class Checkbox(TkBaseWidget):
5
+ def __init__(self, text, model):
6
+ super().__init__()
7
+ self.text = text
8
+ self.model = model
9
+
10
+ def update(self, prev):
11
+ if prev and prev.ui:
12
+ self.var = prev.var
13
+ self.ui = prev.ui
14
+ else:
15
+ self.var = tk.IntVar()
16
+ self.ui = tk.Checkbutton(self.tkparent.inner, text=self.text, variable=self.var, anchor="w")
17
+ self.ui.configure(command=self._select)
18
+ if self.model.value:
19
+ self.ui.select()
20
+ else:
21
+ self.ui.deselect()
22
+
23
+ super().update(prev)
24
+
25
+ def _select(self):
26
+ node = self.get_node()
27
+ self.model.value = node.var.get() != 0
PUI/tkinter/label.py ADDED
@@ -0,0 +1,17 @@
1
+ from .. import *
2
+ from .base import *
3
+ class Label(TkBaseWidget):
4
+ def __init__(self, text, selectable=False):
5
+ super().__init__()
6
+ self.text = text
7
+
8
+ def update(self, prev):
9
+ if prev and prev.ui:
10
+ self.ui = prev.ui
11
+ self.ui.config(text = self.text)
12
+ else:
13
+ self.ui = tk.Label(self.tkparent.inner, text=self.text, anchor="w", justify="left")
14
+ self.ui.bind("<Button-1>", self._clicked)
15
+ if self._onClicked:
16
+ self.ui.config(cursor="")
17
+ super().update(prev)
PUI/tkinter/layout.py ADDED
@@ -0,0 +1,114 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class HBox(TkBaseWidget):
5
+ pui_terminal = False
6
+
7
+ use_ttk = "TFrame"
8
+ def update(self, prev):
9
+ if prev and prev.ui:
10
+ self.ui = prev.ui
11
+ self.ui_children = prev.ui_children
12
+ self.ui_gridmap = prev.ui_gridmap
13
+ else:
14
+ self.ui = ttk.Frame(self.tkparent.inner)
15
+ self.ui.grid_rowconfigure(0, weight=1)
16
+ self.ui_children = []
17
+ self.ui_gridmap = set()
18
+ super().update(prev)
19
+
20
+ def addChild(self, idx, child):
21
+ self.ui_children.insert(idx, child)
22
+
23
+ def removeChild(self, idx, child):
24
+ self.ui_children.pop(idx)
25
+ if isinstance(child, TkBaseWidget):
26
+ child_outer = child.outer
27
+ if child_outer:
28
+ child_outer.grid_forget()
29
+
30
+ def putChild(self, idx, child):
31
+ if isinstance(child, TkBaseWidget):
32
+ child.outer.grid(row=0, column=idx, sticky='nsew')
33
+ if child.layout_weight is None:
34
+ self.ui.grid_columnconfigure(idx, weight=0)
35
+ self.ui_gridmap.discard(idx)
36
+ else:
37
+ self.ui.grid_columnconfigure(idx, weight=child.layout_weight, uniform=".")
38
+ self.ui_gridmap.add(idx)
39
+
40
+ def postSync(self):
41
+ n = len(self.ui_children)
42
+ tbd = []
43
+ for i in self.ui_gridmap:
44
+ if i >= n:
45
+ self.ui.grid_columnconfigure(i, weight=0)
46
+ tbd.append(i)
47
+ for i in tbd:
48
+ self.ui_gridmap.discard(i)
49
+
50
+ for i,child in enumerate(self.ui_children):
51
+ self.putChild(i, child)
52
+ super().postSync()
53
+
54
+ class VBox(TkBaseWidget):
55
+ pui_terminal = False
56
+
57
+ use_ttk = "TFrame"
58
+ def update(self, prev):
59
+ if prev and prev.ui:
60
+ self.ui = prev.ui
61
+ self.ui_children = prev.ui_children
62
+ self.ui_gridmap = prev.ui_gridmap
63
+ else:
64
+ self.ui = ttk.Frame(self.tkparent.inner)
65
+ self.ui.grid_columnconfigure(0, weight=1)
66
+ self.ui_children = []
67
+ self.ui_gridmap = set()
68
+ super().update(prev)
69
+
70
+ def addChild(self, idx, child):
71
+ self.ui_children.insert(idx, child)
72
+
73
+ def removeChild(self, idx, child):
74
+ self.ui_children.pop(idx)
75
+ if isinstance(child, TkBaseWidget):
76
+ child_outer = child.outer
77
+ if child_outer:
78
+ child_outer.grid_forget()
79
+
80
+ def putChild(self, idx, child):
81
+ if isinstance(child, TkBaseWidget):
82
+ child.outer.grid(row=idx, column=0, sticky='nsew')
83
+ if child.layout_weight is None:
84
+ self.ui.grid_rowconfigure(idx, weight=0)
85
+ self.ui_gridmap.discard(idx)
86
+ else:
87
+ self.ui.grid_rowconfigure(idx, weight=child.layout_weight, uniform=".")
88
+ self.ui_gridmap.add(idx)
89
+
90
+ def postSync(self):
91
+ n = len(self.ui_children)
92
+ tbd = []
93
+ for i in self.ui_gridmap:
94
+ if i >= n:
95
+ self.ui.grid_rowconfigure(i, weight=0)
96
+ tbd.append(i)
97
+ for i in tbd:
98
+ self.ui_gridmap.discard(i)
99
+
100
+ for i,child in enumerate(self.ui_children):
101
+ self.putChild(i, child)
102
+ super().postSync()
103
+
104
+ class Spacer(TkBaseWidget):
105
+ use_ttk = "TFrame"
106
+ def __init__(self, *args):
107
+ super().__init__(*args)
108
+ self.layout_weight = 1
109
+
110
+ def update(self, prev):
111
+ if prev and prev.ui:
112
+ self.ui = prev.ui
113
+ else:
114
+ self.ui = ttk.Frame(self.tkparent.inner)
@@ -0,0 +1,17 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class ProgressBar(TkBaseWidget):
5
+ def __init__(self, progress, maximum=1):
6
+ super().__init__()
7
+ self.progress = progress*100
8
+ self.maximum = maximum*100
9
+
10
+ def update(self, prev):
11
+ if prev and prev.ui:
12
+ self.ui = prev.ui
13
+ else:
14
+ self.ui = ttk.Progressbar(self.tkparent.inner)
15
+ self.ui["maximum"] = self.maximum
16
+ self.ui["value"] = self.progress
17
+ super().update(prev)
@@ -0,0 +1,26 @@
1
+ from .. import *
2
+ from .base import *
3
+
4
+ class RadioButton(TkBaseWidget):
5
+ def __init__(self, text, value, model):
6
+ super().__init__()
7
+ self.var = None
8
+ self.text = text
9
+ self.value = value
10
+ self.model = model
11
+
12
+ def update(self, prev):
13
+ if prev and prev.ui:
14
+ self.var = prev.var
15
+ self.ui = prev.ui
16
+ else:
17
+ self.var = tk.StringVar()
18
+ self.ui = tk.Radiobutton(self.tkparent.inner, text=self.text, variable=self.var, value=self.value, anchor="w")
19
+
20
+ self.var.set(self.model.value)
21
+ self.ui.configure(command=self._select)
22
+ super().update(prev)
23
+
24
+ def _select(self):
25
+ node = self.get_node()
26
+ self.model.value = node.value
PUI/tkinter/scroll.py ADDED
@@ -0,0 +1,201 @@
1
+ from .. import *
2
+ from .base import *
3
+ import math
4
+
5
+ class ScrollableFrame(ttk.Frame):
6
+ def __init__(self, parent):
7
+ self.vertical = None
8
+ self.horizontal = False
9
+
10
+ self.container = ttk.Frame(parent)
11
+ self.container.rowconfigure(0, weight=1)
12
+ self.container.columnconfigure(0, weight=1)
13
+
14
+ self.scroll_y = tk.Scrollbar(self.container, orient=tk.VERTICAL)
15
+ self.scroll_x = tk.Scrollbar(self.container, orient=tk.HORIZONTAL)
16
+ self.should_scroll_y = False
17
+ self.should_scroll_x = False
18
+ self.last_bbox = None
19
+ self.last_scroll_x = None
20
+ self.last_scroll_y = None
21
+
22
+ self.canvas = tk.Canvas(self.container)
23
+ self.canvas.grid(row=0, column=0, sticky="nsew")
24
+ super().__init__(self.canvas)
25
+ self.canvas.create_window(0, 0, window=self, anchor="nw")
26
+
27
+ self.bind("<Configure>", self._on_configure)
28
+
29
+ def set_scroll(self, vertical, horizontal):
30
+ self.vertical = vertical
31
+ self.horizontal = horizontal
32
+
33
+ self.update_scroller()
34
+
35
+ def grid(self, *args, **kwargs):
36
+ self.container.grid(*args, **kwargs)
37
+
38
+ def configure(self, *args, **kwargs):
39
+ self.container.configure(*args, **kwargs)
40
+
41
+ def _on_configure(self, event):
42
+ self.update_scroller()
43
+
44
+ def update_scroller(self):
45
+ bbox = self.canvas.bbox("all")
46
+ _, _, w, h = bbox
47
+ self.canvas.configure(scrollregion=bbox)
48
+ if self.horizontal is False:
49
+ self.canvas.configure(width=w)
50
+ if self.vertical is False:
51
+ self.canvas.configure(height=h)
52
+
53
+ self.should_scroll_y = self.vertical or (self.vertical is None and h > self.canvas.winfo_height())
54
+ if self.should_scroll_y:
55
+ self.scroll_y.config(command=self.canvas.yview)
56
+ self.canvas.config(yscrollcommand=self._on_scroll_y)
57
+ self.scroll_y.grid(row=0, column=1, sticky="ns")
58
+
59
+ self.canvas.bind_all("<MouseWheel>", self._on_mousewheel_y)
60
+ self.canvas.bind("<Button-4>", self._on_mousewheel_y)
61
+ self.canvas.bind("<Button-5>", self._on_mousewheel_y)
62
+ else:
63
+ self.scroll_y.grid_forget()
64
+
65
+ self.should_scroll_x = self.horizontal or (self.horizontal is None and w > self.canvas.winfo_width())
66
+ if self.should_scroll_x:
67
+ self.scroll_x.config(command=self.canvas.xview)
68
+ self.canvas.config(xscrollcommand=self._on_scroll_x)
69
+ self.scroll_x.grid(row=1, column=0, sticky="ew")
70
+ else:
71
+ self.scroll_x.grid_forget()
72
+
73
+ def _on_scroll_x(self, first, last):
74
+ self.scroll_x.set(first, last)
75
+ bbox = self.canvas.bbox("all")
76
+ if self.last_bbox == bbox and self.last_scroll_x != (first,last):
77
+ if float(first) < 0.05:
78
+ self.puinode.align_x = 0
79
+ elif float(last) > 0.95:
80
+ self.puinode.align_x = 1
81
+ else:
82
+ self.last_bbox = bbox
83
+ self.last_scroll_x = (first,last)
84
+
85
+ def _on_scroll_y(self, first, last):
86
+ self.scroll_y.set(first, last)
87
+ bbox = self.canvas.bbox("all")
88
+ if self.last_bbox == bbox and self.last_scroll_y != (first,last):
89
+ if float(first) < 0.05:
90
+ self.puinode.align_y = 0
91
+ elif float(last) > 0.95:
92
+ self.puinode.align_y = 1
93
+ else:
94
+ self.last_bbox = bbox
95
+ self.last_scroll_y = (first,last)
96
+
97
+ def _on_mousewheel_y(self, event):
98
+ if not self.should_scroll_y:
99
+ return
100
+ if event.num == 5:
101
+ delta = 1
102
+ elif event.num == 4:
103
+ delta = -1
104
+ elif event.delta > 0:
105
+ delta = 1
106
+ elif event.delta < 0:
107
+ delta = -1
108
+ self.canvas.yview_scroll(delta, "units")
109
+
110
+ class Scroll(TkBaseWidget):
111
+ pui_terminal = False
112
+
113
+ use_ttk = "TFrame"
114
+ END = -0.0
115
+ def __init__(self, vertical=None, horizontal=False):
116
+ self.vertical = vertical
117
+ self.horizontal = horizontal
118
+ self.align_x = 0
119
+ self.align_y = 0
120
+ super().__init__()
121
+ self.layout_weight = 1
122
+
123
+ def update(self, prev):
124
+ if prev and prev.ui:
125
+ self.ui = prev.ui
126
+ self.align_x = prev.align_x
127
+ self.align_y = prev.align_y
128
+ else:
129
+ self.ui = ScrollableFrame(self.tkparent.inner)
130
+ self.ui.puinode = self
131
+ self.ui.set_scroll(self.vertical, self.horizontal)
132
+
133
+ super().update(prev)
134
+
135
+ def preSync(self):
136
+ bbox = self.ui.canvas.bbox("all")
137
+ _, _, w, h = bbox
138
+ hsb = self.ui.scroll_x
139
+ if self.align_x == 0:
140
+ self.hsb_offset = hsb.get()[0] * w
141
+ else:
142
+ self.hsb_offset = w - hsb.get()[1] * w
143
+ vsb = self.ui.scroll_y
144
+ if self.align_y == 0:
145
+ self.vsb_offset = vsb.get()[0] * h
146
+ else:
147
+ self.vsb_offset = h - vsb.get()[1] * h
148
+
149
+ def postSync(self):
150
+ self.ui.winfo_toplevel().update_idletasks()
151
+
152
+ oldincx = self.ui.canvas["xscrollincrement"]
153
+ oldincy = self.ui.canvas["yscrollincrement"]
154
+ self.ui.canvas["xscrollincrement"] = 1
155
+ self.ui.canvas["yscrollincrement"] = 1
156
+
157
+ bbox = self.ui.canvas.bbox("all")
158
+ _, _, w, h = bbox
159
+ if self.align_x == 0:
160
+ self.ui.canvas.xview_moveto(0.0)
161
+ self.ui.canvas.xview_scroll(int(self.hsb_offset), "units")
162
+ else:
163
+ self.ui.canvas.xview_moveto(1.0)
164
+ self.ui.canvas.xview_scroll(int(-self.hsb_offset), "units")
165
+ if self.align_y == 0:
166
+ self.ui.canvas.yview_moveto(0.0)
167
+ self.ui.canvas.yview_scroll(int(self.vsb_offset), "units")
168
+ else:
169
+ self.ui.canvas.yview_moveto(1.0)
170
+ self.ui.canvas.yview_scroll(int(-self.vsb_offset), "units")
171
+
172
+ self.ui.canvas["xscrollincrement"] = oldincx
173
+ self.ui.canvas["yscrollincrement"] = oldincy
174
+
175
+ def addChild(self, idx, child):
176
+ if idx:
177
+ return
178
+ child_outer = child.outer
179
+ if child_outer:
180
+ child_outer.grid(row=idx, column=0, sticky='nsew')
181
+
182
+ def removeChild(self, idx, child):
183
+ child.outer.grid_forget()
184
+
185
+ def scrollX(self, pos=0):
186
+ if math.copysign(1, pos) >= 0:
187
+ self.align_x = 0
188
+ self.hsb_offset = pos
189
+ else:
190
+ self.align_x = 1
191
+ self.hsb_offset = abs(pos)
192
+ return self
193
+
194
+ def scrollY(self, pos=0):
195
+ if math.copysign(1, pos) >= 0:
196
+ self.align_y = 0
197
+ self.vsb_offset = pos
198
+ else:
199
+ self.align_y = 1
200
+ self.vsb_offset = abs(pos)
201
+ return self