From bd432c5cc6b07ebedad8c3982df89507f8baa9d7 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Fri, 24 Feb 2023 00:02:53 +0000 Subject: [PATCH] Added transparent canvas support and basic layout support in ttkDesigner --- TermTk/TTkCore/canvas.py | 39 +++- TermTk/TTkWidgets/image.py | 5 +- docs/designer/images/internals.001.svg | 4 + tests/timeit/13.canvas.alpha.py | 114 ++++++++++ ttkDesigner/app/__init__.py | 3 +- ttkDesigner/app/superobj/__init__.py | 26 +++ ttkDesigner/app/superobj/supercontrol.py | 108 +++++++++ ttkDesigner/app/superobj/superlayout.py | 146 ++++++++++++ ttkDesigner/app/superobj/superwidget.py | 201 +++++++++++++++++ ttkDesigner/app/treeinspector.py | 22 +- ttkDesigner/app/widgetbox.py | 10 +- ttkDesigner/app/windoweditor.py | 270 +---------------------- 12 files changed, 657 insertions(+), 291 deletions(-) create mode 100644 docs/designer/images/internals.001.svg create mode 100644 tests/timeit/13.canvas.alpha.py create mode 100644 ttkDesigner/app/superobj/__init__.py create mode 100644 ttkDesigner/app/superobj/supercontrol.py create mode 100644 ttkDesigner/app/superobj/superlayout.py create mode 100644 ttkDesigner/app/superobj/superwidget.py 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 @@ + + + +TTkWidgetlayoutSuperWidget (TTkWidget)widlayoutTTkLayout (layoutType)childrenTTkWidgetlayoutTTkWidgetlayoutTTkLayout (Grid Layout)childrenSuperLayout (TTkWidget)laylayoutTTkLayout (layoutType)childrenSuperWidget (TTkWidget)widlayoutSuperWidget (TTkWidget)widlayout \ 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):