diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py
index b50e3ad4..311a2e5a 100644
--- a/TermTk/TTkCore/canvas.py
+++ b/TermTk/TTkCore/canvas.py
@@ -41,10 +41,11 @@ class TTkCanvas:
'_theme',
'_data', '_colors',
'_bufferedData', '_bufferedColors',
- '_visible', '_doubleBuffer')
+ '_visible', '_transparent', '_doubleBuffer')
def __init__(self, *args, **kwargs):
self._widget = kwargs.get('widget', None)
self._visible = True
+ self._transparent = False
self._doubleBuffer = False
self._width = 0
self._height = 0
@@ -56,6 +57,12 @@ class TTkCanvas:
# self.resize(self._width, self._height)
# TTkLog.debug((self._width, self._height))
+ def transparent(self):
+ return self._transparent
+
+ def setTransparent(self, tr):
+ self._transparent = tr
+
def getWidget(self): return self._widget
def enableDoubleBuffer(self):
@@ -92,8 +99,12 @@ class TTkCanvas:
def clean(self):
if not self._visible: return
w,h = self._width, self._height
- baseData = [' ']*w
- baseColors = [TTkColor.RST]*w
+ if self._transparent:
+ baseData = [None]*w
+ baseColors = baseData
+ else:
+ baseData = [' ']*w
+ baseColors = [TTkColor.RST]*w
self._data = [baseData.copy() for _ in range(h)]
self._colors = [baseColors.copy() for _ in range(h)]
@@ -635,18 +646,30 @@ class TTkCanvas:
hslice = min(h if y+h < by+bh else by+bh-y,canvas._height)
a, b = x+xoffset, x+wslice
- for iy in range(yoffset,hslice):
- self._data[y+iy][a:b] = canvas._data[iy][xoffset:wslice]
- self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice]
+ if canvas._transparent:
+ for iy in range(yoffset,hslice):
+ if None in canvas._data[iy][xoffset:wslice]:
+ self._data[y+iy][a:b] = [cca if cca else ccb for cca,ccb in zip(canvas._data[iy][xoffset:wslice],self._data[y+iy][a:b])]
+ else:
+ self._data[y+iy][a:b] = canvas._data[iy][xoffset:wslice]
+ if None in canvas._colors[iy][xoffset:wslice]:
+ self._colors[y+iy][a:b] = [cca if cca else ccb for cca,ccb in zip(canvas._colors[iy][xoffset:wslice],self._colors[y+iy][a:b])]
+ else:
+ self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice]
+ else:
+ for iy in range(yoffset,hslice):
+ self._data[y+iy][a:b] = canvas._data[iy][xoffset:wslice]
+ self._colors[y+iy][a:b] = canvas._colors[iy][xoffset:wslice]
+
# Check the full wide chars on the edge of the two canvasses
if ((0 <= a < cw) and self._data[y+iy][a]==''):
self._data[y+iy][a] = TTkCfg.theme.unicodeWideOverflowCh[0]
self._colors[y+iy][a] = TTkCfg.theme.unicodeWideOverflowColor
- if ((0 < b <= cw) and TTkString._isWideCharData(self._data[y+iy][b-1])):
+ if ((0 < b <= cw) and self._data[y+iy][b-1] and TTkString._isWideCharData(self._data[y+iy][b-1])):
self._data[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowCh[1]
self._colors[y+iy][b-1] = TTkCfg.theme.unicodeWideOverflowColor
- if ((0 < a <= cw) and TTkString._isWideCharData(self._data[y+iy][a-1])):
+ if ((0 < a <= cw) and self._data[y+iy][a-1] and TTkString._isWideCharData(self._data[y+iy][a-1])):
self._data[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowCh[1]
self._colors[y+iy][a-1] = TTkCfg.theme.unicodeWideOverflowColor
if ((0 <= b < cw) and self._data[y+iy][b]==''):
diff --git a/TermTk/TTkWidgets/image.py b/TermTk/TTkWidgets/image.py
index 1d2df3e9..98f9342c 100644
--- a/TermTk/TTkWidgets/image.py
+++ b/TermTk/TTkWidgets/image.py
@@ -106,10 +106,7 @@ class TTkImage(TTkWidget):
for row in self._data:
for i,pixel in enumerate(row):
h,s,l = TTkColor.rgb2hsl(pixel)
- h += deg
- #TTkLog.debug(f"{h=}")
- if h >= 360: h-=360
- row[i] = TTkColor.hsl2rgb((h,s,l))
+ row[i] = TTkColor.hsl2rgb(((h+deg)%360,s,l))
def paintEvent(self):
img = self._data
diff --git a/docs/designer/images/internals.001.svg b/docs/designer/images/internals.001.svg
new file mode 100644
index 00000000..d2800e99
--- /dev/null
+++ b/docs/designer/images/internals.001.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/timeit/13.canvas.alpha.py b/tests/timeit/13.canvas.alpha.py
new file mode 100644
index 00000000..bea4b34a
--- /dev/null
+++ b/tests/timeit/13.canvas.alpha.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python3
+
+# MIT License
+#
+# Copyright (c) 2021 Eugenio Parodi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import sys, os
+
+import timeit
+import random
+
+
+sys.path.append(os.path.join(sys.path[0],'../..'))
+import TermTk as ttk
+
+w=0x1000
+h=0x1000
+
+c1 = [[0x1234]*w for _ in range(h)]
+c2 = [[0x4321,None]*(w//2) for _ in range(h//2)]+[[0x5678]*w for _ in range(h//2)]
+c3 = [[0x5678]*((w)-1)+[None] for _ in range(h)]
+
+
+
+def paintCanvasNew1():
+ d1,d2 = [c.copy() for c in c1], [c.copy() for c in c2]
+ a,b = 0x10,w-0x10
+ for iy in range(0x10,h-0x10):
+ d1[iy][a:b] = [x if x else y for x,y in zip(d2[iy][a:b],d1[iy][a:b])]
+
+def paintCanvasNew2():
+ d1,d2 = [c.copy() for c in c1], [c.copy() for c in c2]
+ a,b = 0x10,w-0x10
+ for iy in range(0x10,h-0x10):
+ if None in d2[iy]:
+ d1[iy][a:b] = [x if x else y for x,y in zip(d2[iy][a:b],d1[iy][a:b])]
+ else:
+ d1[iy][a:b] = d2[iy][a:b]
+
+def paintCanvasNew3():
+ d1,d2 = [c.copy() for c in c1], [c.copy() for c in c1]
+ a,b = 0x10,w-0x10
+ for iy in range(0x10,h-0x10):
+ if None in d2[iy]:
+ d1[iy][a:b] = [x if x else y for x,y in zip(d2[iy][a:b],d1[iy][a:b])]
+ else:
+ d1[iy][a:b] = d2[iy][a:b]
+
+def paintCanvasNew4():
+ d1,d2 = [c.copy() for c in c1], [c.copy() for c in c3]
+ a,b = 0x10,w-0x10
+ for iy in range(0x10,h-0x10):
+ if None in d2[iy]:
+ d1[iy][a:b] = [x if x else y for x,y in zip(d2[iy][a:b],d1[iy][a:b])]
+ else:
+ d1[iy][a:b] = d2[iy][a:b]
+
+def paintCanvasOld():
+ d1,d2 = [c.copy() for c in c1], [c.copy() for c in c2]
+ a,b = 0x10,w-0x10
+ for iy in range(0x10,h-0x10):
+ d1[iy][a:b] = d2[iy][a:b]
+
+def _re(s):
+ paintCanvasOld()
+def _old(s):
+ paintCanvasNew1()
+
+def test1(v): return paintCanvasOld()
+def test2(v): return paintCanvasNew1()
+def test3(v): return paintCanvasNew2()
+def test4(v): return paintCanvasNew3()
+def test5(v): return paintCanvasNew4()
+def test6(v): return _old(v)
+def test7(v): return _re(v)
+def test8(v): return _old(v)
+
+loop=10
+
+a=1
+result = timeit.timeit('test1(a)', globals=globals(), number=loop)
+print(f"1a s {result / loop:.10f} - {result / loop} {test1(a)}")
+result = timeit.timeit('test2(a)', globals=globals(), number=loop)
+print(f"2a {result / loop:.10f} - {result / loop} {test2(a)}")
+result = timeit.timeit('test3(a)', globals=globals(), number=loop)
+print(f"3b s {result / loop:.10f} - {result / loop} {test3(a)}")
+result = timeit.timeit('test4(a)', globals=globals(), number=loop)
+print(f"4b {result / loop:.10f} - {result / loop} {test4(a)}")
+result = timeit.timeit('test5(a)', globals=globals(), number=loop)
+print(f"5c s {result / loop:.10f} - {result / loop} {test5(a)}")
+result = timeit.timeit('test6(a)', globals=globals(), number=loop)
+print(f"6c {result / loop:.10f} - {result / loop} {test6(a)}")
+result = timeit.timeit('test7(a)', globals=globals(), number=loop)
+print(f"7d s {result / loop:.10f} - {result / loop} {test7(a)}")
+result = timeit.timeit('test8(a)', globals=globals(), number=loop)
+print(f"8d {result / loop:.10f} - {result / loop} {test8(a)}")
diff --git a/ttkDesigner/app/__init__.py b/ttkDesigner/app/__init__.py
index 9e66d098..46c53156 100644
--- a/ttkDesigner/app/__init__.py
+++ b/ttkDesigner/app/__init__.py
@@ -27,5 +27,6 @@ from .main import main
from .designer import TTkDesigner
from .treeinspector import TreeInspector
from .widgetbox import DragDesignItem, WidgetBox
-from .windoweditor import WindowEditor, SuperWidget
+from .windoweditor import WindowEditor
from .propertyeditor import PropertyEditor
+from .superobj import *
diff --git a/ttkDesigner/app/superobj/__init__.py b/ttkDesigner/app/superobj/__init__.py
new file mode 100644
index 00000000..6b91d28e
--- /dev/null
+++ b/ttkDesigner/app/superobj/__init__.py
@@ -0,0 +1,26 @@
+
+# MIT License
+#
+# Copyright (c) 2023 Eugenio Parodi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from .supercontrol import SuperControlWidget
+from .superwidget import SuperWidget
+from .superlayout import SuperLayout
\ No newline at end of file
diff --git a/ttkDesigner/app/superobj/supercontrol.py b/ttkDesigner/app/superobj/supercontrol.py
new file mode 100644
index 00000000..77ac6a8c
--- /dev/null
+++ b/ttkDesigner/app/superobj/supercontrol.py
@@ -0,0 +1,108 @@
+# MIT License
+#
+# Copyright (c) 2023 Eugenio Parodi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import TermTk as ttk
+
+class SuperControlWidget(ttk.TTkResizableFrame):
+ def __init__(self, wid, *args, **kwargs):
+ self._wid = wid
+ self._widPos = self._wid.pos()
+ self._draggable = False
+ self._mouseDelta = (0,0)
+ kwargs['maxSize'] = [v+2 for v in wid.maximumSize()]
+ kwargs['minSize'] = [v+2 for v in wid.minimumSize()]
+ kwargs['size'] = [v+2 for v in wid.size() ]
+ super().__init__(*args, **kwargs)
+ self.getCanvas().setTransparent(True)
+
+ def _alignWidToPos(self, pos):
+ x,y = self.pos()
+ ox,oy = pos
+ wx,wy = self._wid.pos()
+ self._wid.move(wx+x-ox, wy+y-oy)
+ self.update()
+ return super().move(x,y)
+
+ def resizeEvent(self, w, h):
+ self._wid.resize(w-2,h-2)
+ self._wid._canvas.updateSize()
+ return super().resizeEvent(w, h)
+
+ def mouseReleaseEvent(self, evt) -> bool:
+ self._draggable = False
+ return super().mouseReleaseEvent(evt)
+
+ def mousePressEvent(self, evt):
+ self._draggable = False
+ self._mouseDelta = (evt.x, evt.y)
+ w,h = self.size()
+ x,y = evt.x, evt.y
+ if x==0 or x==w-1 or y==0 or y==h-1:
+ return super().mousePressEvent(evt)
+ self._draggable = True
+ return True
+
+ def mouseDragEvent(self, evt):
+ bkPos = self.pos()
+ if self._draggable:
+ x,y = self.pos()
+ dx = evt.x-self._mouseDelta[0]
+ dy = evt.y-self._mouseDelta[1]
+ self.move(x+dx, y+dy)
+ self._alignWidToPos(bkPos)
+ return True
+ ret = super().mouseDragEvent(evt)
+ self._alignWidToPos(bkPos)
+ return ret
+
+ def keyEvent(self, evt):
+ if evt.type == ttk.TTkK.SpecialKey:
+ if evt.key in (ttk.TTkK.Key_Delete, ttk.TTkK.Key_Backspace) :
+ self._wid.close()
+ self.close()
+ self._wid.weModified.emit()
+ return True
+ bkPos = self.pos()
+ x,y = 0,0
+ if evt.key == ttk.TTkK.Key_Up: y=-1
+ elif evt.key == ttk.TTkK.Key_Down: y=1
+ elif evt.key == ttk.TTkK.Key_Left: x=-1
+ elif evt.key == ttk.TTkK.Key_Right: x=1
+ if any((x,y)):
+ self.move(bkPos[0]+x, bkPos[1]+y)
+ self._alignWidToPos(bkPos)
+ return True
+
+ def paintEvent(self):
+ w,h = self.size()
+ self._wid.paintEvent()
+ self._wid.paintChildCanvas()
+ self._canvas.paintCanvas(
+ self._wid.getCanvas(),
+ ( 1, 1, w, h), # geometry
+ ( 0, 0, w, h), # slice
+ ( 0, 0, w, h)) # bound
+ self._canvas.drawBox(pos=(0,0),size=self.size())
+ self._canvas.drawChar(pos=( 0, 0), char='▛')
+ self._canvas.drawChar(pos=(w-1, 0), char='▜')
+ self._canvas.drawChar(pos=( 0, h-1), char='▙')
+ self._canvas.drawChar(pos=(w-1, h-1), char='▟')
diff --git a/ttkDesigner/app/superobj/superlayout.py b/ttkDesigner/app/superobj/superlayout.py
new file mode 100644
index 00000000..a7845715
--- /dev/null
+++ b/ttkDesigner/app/superobj/superlayout.py
@@ -0,0 +1,146 @@
+# MIT License
+#
+# Copyright (c) 2023 Eugenio Parodi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from random import randint
+
+import TermTk as ttk
+import ttkDesigner.app.superobj as so
+
+class SuperLayout(ttk.TTkWidget):
+ def __init__(self, lay, weModified, widgetSelected, *args, **kwargs):
+ self.weModified = weModified
+ self.widgetSelected = widgetSelected
+ self._lay = lay
+ self._superRootWidget = kwargs.get('superRootWidget',False)
+ self._selectable = kwargs.get('selectable', False)
+
+ # kwargs['pos'] = (x,y) = lay.pos()
+ x,y = kwargs.get('pos',lay.pos())
+ kwargs['size'] = (w,h) = lay.size()
+ self._lay.setGeometry(x,y,w,h)
+
+ super().__init__(*args, **kwargs)
+
+ self.getCanvas().setTransparent(True)
+ # r,g,b = randint(0,0xFF),randint(0,0xFF),randint(0,0xFF)
+ # self._layoutColor = ttk.TTkColor.bg(f"#{r:02X}{g:02X}{b:02X}")
+ self.setFocusPolicy(ttk.TTkK.ClickFocus)
+ so.SuperWidget.toggleHighlightLayout.connect(self._toggleHighlightLayout)
+
+ @ttk.pyTTkSlot(bool)
+ def _toggleHighlightLayout(self, state):
+ so.SuperWidget._showLayout = state
+ self.update()
+
+ def dumpDict(self):
+ ret = {}
+ return ret
+
+ def updateAll(self):
+ self.resize(*(self._lay.size()))
+ self.move(*(self._lay.pos()))
+ # self.setPadding(*(self._lay.getPadding()))
+ self.setMaximumSize(*(self._lay.maximumSize()))
+ self.setMinimumSize(*(self._lay.minimumSize()))
+ self.update()
+
+ def mousePressEvent(self, evt) -> bool:
+ return self._selectable and not self._superRootWidget
+
+ def pushSuperControlWidget(self):
+ if self._superRootWidget or not self._selectable: return False
+ scw = so.SuperControlWidget(self)
+ ttk.TTkHelper.removeOverlay()
+ ttk.TTkHelper.overlay(self, scw, -1,-1, forceBoundaries=False)
+
+ def mouseReleaseEvent(self, evt) -> bool:
+ if self._superRootWidget or not self._selectable: return False
+ self.pushSuperControlWidget()
+ # self.widgetSelected.emit(self._lay,self)
+ return True
+
+ def mouseDragEvent(self, evt) -> bool:
+ if self._superRootWidget or not self._selectable: return False
+ drag = ttk.TTkDrag()
+ data = self
+ canvas = self.getCanvas()
+ canvas.clean()
+ ttk.TTkWidget._paintChildCanvas(canvas, self._lay, self._lay.geometry(), self._lay.offset())
+ drag.setHotSpot(evt.x, evt.y)
+ drag.setPixmap(canvas)
+ drag.setData(data)
+ drag.exec()
+ self.parentWidget().layout().removeWidget(self)
+ self.parentWidget().update()
+ return True
+
+ def dropEvent(self, evt) -> bool:
+ data = evt.data()
+ hsx,hsy = evt.hotSpot()
+ ttk.TTkLog.debug(f"Drop ({data.__class__.__name__}) -> pos={evt.pos()}")
+ if issubclass(type(data),ttk.TTkLayout):
+ self.layout().addWidget(sw := so.SuperLayout(lay=data, weModified=self.weModified, widgetSelected=self.widgetSelected, pos=(evt.x-hsx, evt.y-hsy), selectable=True))
+ self._lay.addItem(data)
+ elif issubclass(type(data), so.SuperLayout):
+ sw = data
+ self.layout().addWidget(sw)
+ data = data._lay
+ self._lay.addItem(data)
+ sw.show()
+ sw.move(evt.x-hsx, evt.y-hsy)
+ elif issubclass(type(data), so.SuperWidget):
+ sw = data
+ self.layout().addWidget(sw)
+ data = data._wid
+ sw.move(evt.x-hsx, evt.y-hsy)
+ sw.show()
+ self._lay.addWidget(data)
+ data.move(evt.x-hsx, evt.y-hsy)
+ elif issubclass(type(data),ttk.TTkWidget):
+ self.layout().addWidget(sw := so.SuperWidget(wid=data, weModified=self.weModified, widgetSelected=self.widgetSelected, pos=(evt.x-hsx, evt.y-hsy)))
+ self._lay.addWidget(data)
+ data.move(evt.x-hsx, evt.y-hsy)
+ else:
+ return False
+ self.update()
+ self.weModified.emit()
+ return True
+
+ def move(self, x: int, y: int):
+ w,h = self._lay.size()
+ self._lay.setGeometry(x,y,w,h)
+ # self.update()
+ return super().move(x, y)
+
+ def resizeEvent(self, w, h):
+ x,y = self._lay.pos()
+ self._lay.setGeometry(x,y,w,h)
+ return super().resizeEvent(w, h)
+
+ def paintEvent(self):
+ if self._selectable:
+ if so.SuperWidget._showLayout:
+ w,h = self.size()
+ self._canvas.drawBox(pos=(0,0),size=(w,h), color=ttk.TTkColor.fg('#88DD88', modifier=ttk.TTkColorGradient(increment=+1)))
+ else:
+ w,h = self.size()
+ self._canvas.drawBox(pos=(0,0),size=(w,h), color=ttk.TTkColor.fg('#223322', modifier=ttk.TTkColorGradient(increment=+1)))
diff --git a/ttkDesigner/app/superobj/superwidget.py b/ttkDesigner/app/superobj/superwidget.py
new file mode 100644
index 00000000..52416b53
--- /dev/null
+++ b/ttkDesigner/app/superobj/superwidget.py
@@ -0,0 +1,201 @@
+# MIT License
+#
+# Copyright (c) 2023 Eugenio Parodi
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from random import randint
+
+import TermTk as ttk
+
+import ttkDesigner.app.superobj as so
+
+class SuperWidget(ttk.TTkWidget):
+ def __init__(self, wid, weModified, widgetSelected, *args, **kwargs):
+ self.weModified = weModified
+ self.widgetSelected = widgetSelected
+ self._wid = wid
+ self._wid.move(*kwargs['pos'])
+ self._wid._canvas.show()
+ self._superLayout = so.SuperLayout(lay=self._wid.layout(), weModified=self.weModified, widgetSelected=self.widgetSelected,)
+ self._superRootWidget = kwargs.get('superRootWidget',False)
+ kwargs['layout'] = ttk.TTkGridLayout()
+ kwargs['layout'].addWidget(self._superLayout)
+ kwargs['maxSize'] = wid.maximumSize()
+ kwargs['minSize'] = wid.minimumSize()
+ kwargs['size'] = wid.size()
+ padt, padb, padl, padr = wid.getPadding()
+ kwargs['paddingTop'] = padt
+ kwargs['paddingBottom'] = padb
+ kwargs['paddingLeft'] = padl
+ kwargs['paddingRight'] = padr
+ super().__init__(*args, **kwargs)
+ #self.resize(*self._wid.size())
+ h,s,l = randint(0,359),100,80
+ r,g,b = ttk.TTkColor.hsl2rgb(((h+5)%360,s,l))
+ self._layoutColor = ttk.TTkColor.bg(f"#{r:02X}{g:02X}{b:02X}", modifier=ttk.TTkColorGradient(increment=+2))
+ r,g,b = ttk.TTkColor.hsl2rgb(((h+5)%360,s,l))
+ self._layoutPadColor = ttk.TTkColor.bg(f"#{r:02X}{g:02X}{b:02X}", modifier=ttk.TTkColorGradient(increment=-2))
+ self.setFocusPolicy(ttk.TTkK.ClickFocus)
+ SuperWidget.toggleHighlightLayout.connect(self._toggleHighlightLayout)
+
+ _showLayout = False
+ toggleHighlightLayout = ttk.pyTTkSignal(bool)
+
+ @ttk.pyTTkSlot(bool)
+ def _toggleHighlightLayout(self, state):
+ SuperWidget._showLayout = state
+ self.update()
+
+
+ def dumpDict(self):
+ wid = self._wid
+ def _dumpPrimitive(val):
+ return val
+ def _dumpTTkString(val):
+ return val.toAnsi()
+ def _dumpTTkColor(val):
+ return str(val)
+ def _dumpTTkLayout(val):
+ return type(val).__name__
+ def _dumpFlag(val):
+ return val
+ def _dumpList(val, propType):
+ ret = []
+ for i,t in enumerate(propType):
+ if t['type'] in (int,str,float,bool):
+ ret.append(_dumpPrimitive(val[i]))
+ elif type(t['type']) in (list,tuple):
+ ttk.TTkLog.warn("Feature not Implemented yet")
+ elif t['type'] is ttk.TTkLayout:
+ ret.append(_dumpTTkLayout(val[i]))
+ elif t['type'] in (ttk.TTkString,'singleLineTTkString'):
+ ret.append(_dumpTTkString(val[i]))
+ elif t['type'] is ttk.TTkColor:
+ ret.append(_dumpTTkColor(val[i]))
+ elif t['type'] in ('singleFlag','multiFlag'):
+ ret.append(_dumpFlag(val[i]))
+ else:
+ ttk.TTkLog.warn("Type not Recognised")
+ return ret
+ children = []
+ for w in self.layout().children():
+ children.append(w.widget().dumpDict())
+ params = {}
+ for cc in reversed(type(wid).__mro__):
+ # if hasattr(cc,'_ttkProperties'):
+ if issubclass(cc, ttk.TTkWidget):
+ ccName = cc.__name__
+ if ccName in ttk.TTkUiProperties:
+ for p in ttk.TTkUiProperties[ccName]:
+ prop = ttk.TTkUiProperties[ccName][p]
+ propType = prop['get']['type']
+ propCb = prop['get']['cb']
+ # ttk.TTkLog.debug(ccName)
+ if propType in (int,str,float,bool):
+ params |= {p: _dumpPrimitive(propCb(wid))}
+ elif type(propType) in (list,tuple):
+ params |= {p: _dumpList(propCb(wid), propType)}
+ elif propType is ttk.TTkLayout:
+ params |= {p: _dumpTTkLayout(propCb(wid))}
+ elif propType in (ttk.TTkString,'singleLineTTkString'):
+ params |= {p: _dumpTTkString(propCb(wid))}
+ elif propType is ttk.TTkColor:
+ params |= {p: _dumpTTkColor(propCb(wid))}
+ elif propType in ('singleflag','multiflags'):
+ params |= {p: _dumpFlag(propCb(wid))}
+ else:
+ ttk.TTkLog.warn("Type not Recognised")
+
+ ret = {
+ 'class' : wid.__class__.__name__,
+ 'params' : params,
+ 'children': children
+ }
+ return ret
+
+ def updateAll(self):
+ self.resize(*(self._wid.size()))
+ self.move(*(self._wid.pos()))
+ self.setPadding(*(self._wid.getPadding()))
+ self.setMaximumSize(*(self._wid.maximumSize()))
+ self.setMinimumSize(*(self._wid.minimumSize()))
+ self.update()
+
+ def mousePressEvent(self, evt) -> bool:
+ return True
+
+ def pushSuperControlWidget(self):
+ if self._superRootWidget: return False
+ scw = so.SuperControlWidget(self)
+ ttk.TTkHelper.removeOverlay()
+ ttk.TTkHelper.overlay(self, scw, -1,-1, forceBoundaries=False)
+
+ def mouseReleaseEvent(self, evt) -> bool:
+ self.pushSuperControlWidget()
+ self.widgetSelected.emit(self._wid,self)
+ return True
+
+ def mouseDragEvent(self, evt) -> bool:
+ if self._superRootWidget: return False
+ drag = ttk.TTkDrag()
+ data = self
+ data.paintChildCanvas()
+ drag.setHotSpot(evt.x, evt.y)
+ drag.setPixmap(data.getCanvas())
+ drag.setData(data)
+ drag.exec()
+ self.parentWidget().layout().removeWidget(self)
+ self.parentWidget().update()
+ return True
+
+ def dropEvent(self, evt) -> bool:
+ padt, padb, padl, padr = self._wid.getPadding()
+ # evt = evt.copy()
+ evt.x-=padl
+ evt.y-=padt
+ return self._superLayout.dropEvent(evt)
+
+ def move(self, x: int, y: int):
+ self._wid.move(x,y)
+ self.update()
+ return super().move(x, y)
+
+ def resizeEvent(self, w, h):
+ self._wid.resize(w,h)
+ self._wid._canvas.updateSize()
+ return super().resizeEvent(w, h)
+
+ def paintEvent(self):
+ w,h = self.size()
+ if SuperWidget._showLayout:
+ t,b,l,r = self._wid.getPadding()
+ for y in range(h):
+ self._canvas.drawText(pos=(0,y),text='',width=w,color=self._layoutColor)
+ for y in range(t,h-b):
+ self._canvas.drawText(pos=(l,y),text='',width=w-r-l,color=self._layoutPadColor)
+ # self._canvas.fill(color=self._layoutColor)
+ # self._canvas.fill(pos=(l,t), size=(w-r-l,h-b-t), color=self._layoutPadColor)
+ else:
+ self._wid.paintEvent()
+ self._canvas.paintCanvas(
+ self._wid.getCanvas(),
+ ( 0, 0, w, h), # geometry
+ ( 0, 0, w, h), # slice
+ ( 0, 0, w, h)) # bound
\ No newline at end of file
diff --git a/ttkDesigner/app/treeinspector.py b/ttkDesigner/app/treeinspector.py
index 643e0c9a..295a6b9c 100644
--- a/ttkDesigner/app/treeinspector.py
+++ b/ttkDesigner/app/treeinspector.py
@@ -102,15 +102,19 @@ class TreeInspector(ttk.TTkGridLayout):
tomWidget=widget,
tomSuperWidget=superWidget,
expanded=expanded)
- for c in superWidget.layout().children():
- top.addChild(TreeInspector._getTomTreeItem(c,widSelected))
-
- for c in widget.rootLayout().children():
- if c == widget.layout(): continue
- if c.layoutItemType == ttk.TTkK.LayoutItem:
- top.addChild(tc:=_TTkTomTreeWidgetItem(["layout (Other)", c.__class__.__name__, ""]))
- for cc in c.children():
- tc.addChild(TreeInspector._getTomTreeItem(cc,widSelected))
+ if issubclass(type(superWidget), SuperWidget):
+ for c in superWidget._superLayout.layout().children():
+ top.addChild(TreeInspector._getTomTreeItem(c,widSelected))
+ else:
+ for c in superWidget.layout().children():
+ top.addChild(TreeInspector._getTomTreeItem(c,widSelected))
+
+ # for c in widget.rootLayout().children():
+ # if c == widget.layout(): continue
+ # if c.layoutItemType == ttk.TTkK.LayoutItem:
+ # top.addChild(tc:=_TTkTomTreeWidgetItem(["layout (Other)", c.__class__.__name__, ""]))
+ # for cc in c.children():
+ # tc.addChild(TreeInspector._getTomTreeItem(cc,widSelected))
return top
if layoutItem.layoutItemType == ttk.TTkK.LayoutItem:
diff --git a/ttkDesigner/app/widgetbox.py b/ttkDesigner/app/widgetbox.py
index 7e097401..669be825 100644
--- a/ttkDesigner/app/widgetbox.py
+++ b/ttkDesigner/app/widgetbox.py
@@ -27,7 +27,7 @@ from .about import *
dWidgets = {
'Layouts':{
- "Layout" : { "class":ttk.TTkLayout , "params":{}},
+ "Layout" : { "class":ttk.TTkLayout , "params":{'size':(30,10)}},
"H Box Layout" : { "class":ttk.TTkHBoxLayout, "params":{}},
"V Box Layout" : { "class":ttk.TTkVBoxLayout, "params":{}},
"Grid Layout" : { "class":ttk.TTkGridLayout, "params":{}},
@@ -87,7 +87,13 @@ class DragDesignItem(ttk.TTkWidget):
name = f"{name}-{DragDesignItem._objNames[name]}"
drag = ttk.TTkDrag()
data = wc['class'](**(wc['params']|{'name':name}))
- drag.setPixmap(data)
+ if issubclass(wc['class'], ttk.TTkWidget):
+ drag.setPixmap(data)
+ else:
+ w,h = wc['params']['size']
+ pm = ttk.TTkCanvas(width=w, height=h)
+ pm.drawBox(pos=(0,0),size=(w,h), color=ttk.TTkColor.fg('#888888'))
+ drag.setPixmap(pm)
drag.setData(data)
drag.exec()
return True
diff --git a/ttkDesigner/app/windoweditor.py b/ttkDesigner/app/windoweditor.py
index bc3a2e53..be123ae5 100644
--- a/ttkDesigner/app/windoweditor.py
+++ b/ttkDesigner/app/windoweditor.py
@@ -23,273 +23,11 @@
# Yaml is not included by default
# import yaml
import json
-from random import randint
-import TermTk as ttk
-
-class SuperControlWidget(ttk.TTkResizableFrame):
- def __init__(self, wid, *args, **kwargs):
- self._wid = wid
- self._widPos = self._wid.pos()
- self._draggable = False
- self._mouseDelta = (0,0)
- kwargs['maxSize'] = [v+2 for v in wid.maximumSize()]
- kwargs['minSize'] = [v+2 for v in wid.minimumSize()]
- kwargs['size'] = [v+2 for v in wid.size() ]
- super().__init__(*args, **kwargs)
-
- def _alignWidToPos(self, pos):
- x,y = self.pos()
- ox,oy = pos
- wx,wy = self._wid.pos()
- self._wid.move(wx+x-ox, wy+y-oy)
- self.update()
- return super().move(x,y)
-
- def resizeEvent(self, w, h):
- self._wid.resize(w-2,h-2)
- self._wid._canvas.updateSize()
- return super().resizeEvent(w, h)
-
- def mouseReleaseEvent(self, evt) -> bool:
- self._draggable = False
- return super().mouseReleaseEvent(evt)
-
- def mousePressEvent(self, evt):
- self._draggable = False
- self._mouseDelta = (evt.x, evt.y)
- w,h = self.size()
- x,y = evt.x, evt.y
- if x==0 or x==w-1 or y==0 or y==h-1:
- return super().mousePressEvent(evt)
- self._draggable = True
- return True
-
- def mouseDragEvent(self, evt):
- bkPos = self.pos()
- if self._draggable:
- x,y = self.pos()
- dx = evt.x-self._mouseDelta[0]
- dy = evt.y-self._mouseDelta[1]
- self.move(x+dx, y+dy)
- self._alignWidToPos(bkPos)
- return True
- ret = super().mouseDragEvent(evt)
- self._alignWidToPos(bkPos)
- return ret
-
- def keyEvent(self, evt):
- if evt.type == ttk.TTkK.SpecialKey:
- if evt.key in (ttk.TTkK.Key_Delete, ttk.TTkK.Key_Backspace) :
- self._wid.close()
- self.close()
- self._wid.weModified.emit()
- return True
- bkPos = self.pos()
- x,y = 0,0
- if evt.key == ttk.TTkK.Key_Up: y=-1
- elif evt.key == ttk.TTkK.Key_Down: y=1
- elif evt.key == ttk.TTkK.Key_Left: x=-1
- elif evt.key == ttk.TTkK.Key_Right: x=1
- if any((x,y)):
- self.move(bkPos[0]+x, bkPos[1]+y)
- self._alignWidToPos(bkPos)
- return True
-
- def paintEvent(self):
- w,h = self.size()
- self._wid.paintEvent()
- self._wid.paintChildCanvas()
- self._canvas.paintCanvas(
- self._wid.getCanvas(),
- ( 1, 1, w, h), # geometry
- ( 0, 0, w, h), # slice
- ( 0, 0, w, h)) # bound
- self._canvas.drawBox(pos=(0,0),size=self.size())
- self._canvas.drawChar(pos=( 0, 0), char='▛')
- self._canvas.drawChar(pos=(w-1, 0), char='▜')
- self._canvas.drawChar(pos=( 0, h-1), char='▙')
- self._canvas.drawChar(pos=(w-1, h-1), char='▟')
-
-class SuperWidget(ttk.TTkWidget):
- def __init__(self, wid, *args, **kwargs):
- self._wid = wid
- self._wid.move(*kwargs['pos'])
- self._wid._canvas.show()
- self._superRootWidget = kwargs.get('superRootWidget',False)
- kwargs['maxSize'] = wid.maximumSize()
- kwargs['minSize'] = wid.minimumSize()
- kwargs['size'] = wid.size()
- padt, padb, padl, padr = wid.getPadding()
- kwargs['paddingTop'] = padt
- kwargs['paddingBottom'] = padb
- kwargs['paddingLeft'] = padl
- kwargs['paddingRight'] = padr
- super().__init__(*args, **kwargs)
- #self.resize(*self._wid.size())
- r,g,b = randint(0,0xFF),randint(0,0xFF),randint(0,0xFF)
- self._layoutColor = ttk.TTkColor.bg(f"#{r*9//10:02X}{g*9//10:02X}{b*9//10:02X}")
- self._layoutPadColor = ttk.TTkColor.bg(f"#{r:02X}{g:02X}{b:02X}")
- self.setFocusPolicy(ttk.TTkK.ClickFocus)
- SuperWidget.toggleHighlightLayout.connect(self._toggleHighlightLayout)
+from .superobj import SuperWidget
- _showLayout = False
- toggleHighlightLayout = ttk.pyTTkSignal(bool)
-
- @ttk.pyTTkSlot(bool)
- def _toggleHighlightLayout(self, state):
- SuperWidget._showLayout = state
- self.update()
-
-
- def dumpDict(self):
- wid = self._wid
- def _dumpPrimitive(val):
- return val
- def _dumpTTkString(val):
- return val.toAnsi()
- def _dumpTTkColor(val):
- return str(val)
- def _dumpTTkLayout(val):
- return type(val).__name__
- def _dumpFlag(val):
- return val
- def _dumpList(val, propType):
- ret = []
- for i,t in enumerate(propType):
- if t['type'] in (int,str,float,bool):
- ret.append(_dumpPrimitive(val[i]))
- elif type(t['type']) in (list,tuple):
- ttk.TTkLog.warn("Feature not Implemented yet")
- elif t['type'] is ttk.TTkLayout:
- ret.append(_dumpTTkLayout(val[i]))
- elif t['type'] in (ttk.TTkString,'singleLineTTkString'):
- ret.append(_dumpTTkString(val[i]))
- elif t['type'] is ttk.TTkColor:
- ret.append(_dumpTTkColor(val[i]))
- elif t['type'] in ('singleFlag','multiFlag'):
- ret.append(_dumpFlag(val[i]))
- else:
- ttk.TTkLog.warn("Type not Recognised")
- return ret
- children = []
- for w in self.layout().children():
- children.append(w.widget().dumpDict())
- params = {}
- for cc in reversed(type(wid).__mro__):
- # if hasattr(cc,'_ttkProperties'):
- if issubclass(cc, ttk.TTkWidget):
- ccName = cc.__name__
- if ccName in ttk.TTkUiProperties:
- for p in ttk.TTkUiProperties[ccName]:
- prop = ttk.TTkUiProperties[ccName][p]
- propType = prop['get']['type']
- propCb = prop['get']['cb']
- # ttk.TTkLog.debug(ccName)
- if propType in (int,str,float,bool):
- params |= {p: _dumpPrimitive(propCb(wid))}
- elif type(propType) in (list,tuple):
- params |= {p: _dumpList(propCb(wid), propType)}
- elif propType is ttk.TTkLayout:
- params |= {p: _dumpTTkLayout(propCb(wid))}
- elif propType in (ttk.TTkString,'singleLineTTkString'):
- params |= {p: _dumpTTkString(propCb(wid))}
- elif propType is ttk.TTkColor:
- params |= {p: _dumpTTkColor(propCb(wid))}
- elif propType in ('singleflag','multiflags'):
- params |= {p: _dumpFlag(propCb(wid))}
- else:
- ttk.TTkLog.warn("Type not Recognised")
-
- ret = {
- 'class' : wid.__class__.__name__,
- 'params' : params,
- 'children': children
- }
- return ret
-
- def updateAll(self):
- self.resize(*(self._wid.size()))
- self.move(*(self._wid.pos()))
- self.setPadding(*(self._wid.getPadding()))
- self.setMaximumSize(*(self._wid.maximumSize()))
- self.setMinimumSize(*(self._wid.minimumSize()))
- self.update()
-
- def mousePressEvent(self, evt) -> bool:
- return True
-
- def pushSuperControlWidget(self):
- if self._superRootWidget: return False
- scw = SuperControlWidget(self)
- ttk.TTkHelper.removeOverlay()
- ttk.TTkHelper.overlay(self, scw, -1,-1, forceBoundaries=False)
-
- def mouseReleaseEvent(self, evt) -> bool:
- self.pushSuperControlWidget()
- self.widgetSelected.emit(self._wid,self)
- return True
-
- def mouseDragEvent(self, evt) -> bool:
- if self._superRootWidget: return False
- drag = ttk.TTkDrag()
- data = self
- data.paintChildCanvas()
- drag.setHotSpot(evt.x, evt.y)
- drag.setPixmap(data.getCanvas())
- drag.setData(data)
- drag.exec()
- self.parentWidget().layout().removeWidget(self)
- self.parentWidget().update()
- return True
-
- def dropEvent(self, evt) -> bool:
- data = evt.data()
- hsx,hsy = evt.hotSpot()
- padt, padb, padl, padr = self._wid.getPadding()
- ttk.TTkLog.debug(f"Drop ({data.__class__.__name__}) -> pos={evt.pos()}")
- if issubclass(type(data),ttk.TTkWidget):
- if issubclass(type(data), SuperWidget):
- sw = data
- self.layout().addWidget(sw)
- data = data._wid
- sw.move(evt.x-hsx-padl, evt.y-hsy-padt)
- sw.show()
- else:
- self.layout().addWidget(sw := SuperWidget(wid=data, pos=(evt.x-hsx-padl, evt.y-hsy-padt)))
- self._wid.addWidget(data)
- data.move(evt.x-hsx-padl, evt.y-hsy-padt)
- sw.weModified = self.weModified
- sw.widgetSelected = self.widgetSelected
- self.update()
- self.weModified.emit()
- return True
- return False
-
- def move(self, x: int, y: int):
- self._wid.move(x,y)
- self.update()
- return super().move(x, y)
-
- def resizeEvent(self, w, h):
- self._wid.resize(w,h)
- self._wid._canvas.updateSize()
- return super().resizeEvent(w, h)
+import TermTk as ttk
- def paintEvent(self):
- w,h = self.size()
- if SuperWidget._showLayout:
- t,b,l,r = self._wid.getPadding()
- w,h = self.size()
- self._canvas.fill(color=self._layoutColor)
- self._canvas.fill(pos=(l,t), size=(w-r-l,h-b-t), color=self._layoutPadColor)
- else:
- self._wid.paintEvent()
- self._canvas.paintCanvas(
- self._wid.getCanvas(),
- ( 0, 0, w, h), # geometry
- ( 0, 0, w, h), # slice
- ( 0, 0, w, h)) # bound
class WindowEditorView(ttk.TTkAbstractScrollView):
def __init__(self, *args, **kwargs):
@@ -297,9 +35,7 @@ class WindowEditorView(ttk.TTkAbstractScrollView):
self.widgetSelected = ttk.pyTTkSignal(ttk.TTkWidget, ttk.TTkWidget)
super().__init__(*args, **kwargs)
self.viewChanged.connect(self._viewChangedHandler)
- self._ttk = SuperWidget(wid=ttk.TTkWidget(name = 'TTk'), pos=(4,2), size=(self.width()-8,self.height()-4), superRootWidget=True)
- self._ttk.weModified = self.weModified
- self._ttk.widgetSelected = self.widgetSelected
+ self._ttk = SuperWidget(wid=ttk.TTkWidget(name = 'TTk'), weModified=self.weModified, widgetSelected=self.widgetSelected, pos=(4,2), size=(self.width()-8,self.height()-4), superRootWidget=True)
self.layout().addWidget(self._ttk)
def getTTk(self):