diff --git a/TermTk/TTkCore/drag.py b/TermTk/TTkCore/drag.py index 49e7b3e0..0deda824 100644 --- a/TermTk/TTkCore/drag.py +++ b/TermTk/TTkCore/drag.py @@ -35,7 +35,9 @@ class _TTkDragDisplayWidget(TTkWidget): self._x, self._y = TTkHelper.mousePos() def setPixmap(self, pixmap): + w,h = pixmap.size() self._pixmap = pixmap + self.resize(w,h) def paintEvent(self): _,_,w,h = self.geometry() @@ -58,7 +60,13 @@ class TTkDrag(): return self._data def setPixmap(self, pixmap): - self._pixmap = pixmap + if issubclass(type(pixmap),TTkWidget): + pixmap.getCanvas().updateSize() + pixmap.paintEvent() + pixmap = pixmap.getCanvas() + if type(pixmap) is TTkCanvas: + pixmap.updateSize() + self._pixmap.setPixmap(pixmap) def pixmap(self): return self._pixmap @@ -85,25 +93,21 @@ class TTkDrag(): def getDragEnterEvent(self, evt): ret = TTkDropEvent.copy(self) ret._pos = (evt.x, evt.y) + ret.x = evt.x + ret.y = evt.y return ret def getDragLeaveEvent(self, evt): - ret = TTkDropEvent.copy(self) - ret._pos = (evt.x, evt.y) - return ret + return self.getDragEnterEvent(evt) def getDragMoveEvent(self, evt): - ret = TTkDropEvent.copy(self) - ret._pos = (evt.x, evt.y) - return ret + return self.getDragEnterEvent(evt) def getDropEvent(self, evt): - ret = TTkDropEvent.copy(self) - ret._pos = (evt.x, evt.y) - return ret + return self.getDragEnterEvent(evt) class TTkDropEvent(TTkDrag): - __slots__ = ('_pos') + __slots__ = ('_pos', 'x', 'y') def pos(self): return self._pos diff --git a/TermTk/TTkCore/helper.py b/TermTk/TTkCore/helper.py index 8496f3e0..2376ed94 100644 --- a/TermTk/TTkCore/helper.py +++ b/TermTk/TTkCore/helper.py @@ -368,11 +368,11 @@ class TTkHelper: @staticmethod def dndGetDrag(): - return TTkHelper._dnd['d'] + return TTkHelper._dnd['d'] if TTkHelper._dnd else None @staticmethod def dndWidget(): - return TTkHelper._dnd['w'] + return TTkHelper._dnd['w'] if TTkHelper._dnd else None @staticmethod def dndEnter(widget): diff --git a/TermTk/TTkWidgets/image.py b/TermTk/TTkWidgets/image.py index a88adb08..02f3c409 100644 --- a/TermTk/TTkWidgets/image.py +++ b/TermTk/TTkWidgets/image.py @@ -101,6 +101,73 @@ class TTkImage(TTkWidget): # Use Blue as splitter return splitReduce(2) + @staticmethod + def _rgb2hsl(rgb): + r = rgb[0]/255 + g = rgb[1]/255 + b = rgb[2]/255 + cmax = max(r,g,b) + cmin = min(r,g,b) + + lum = (cmax-cmin)/2 + if cmax == cmin: + return 0,0,lum + + delta = cmax-cmin + if cmax == r: + hue = ((g-b)/delta)%6 + elif cmax == g: + hue = (b-r)/delta+2 + else: + hue = (r-g)/delta+4 + + sat = delta / (1 - abs(delta-1)) + hue = int(hue*60) + ( 360 if hue < 0 else 0 ) + sat = int(sat*100) + lum = int(lum*100) + + return hue,sat,lum + + @staticmethod + def _hsl2rgb(hsl): + hue = hsl[0] + sat = hsl[1] / 100 + lum = hsl[2] / 100 + + c = (1-abs(2*lum-1))*sat + x = c*(1-abs((hue/60)%2-1)) + m = lum-c/2 + + if 0 <= hue < 60: + r,g,b = c,x,0 + elif 60 <= hue < 120: + r,g,b = x,c,0 + elif 120 <= hue < 180: + r,g,b = 0,c,x + elif 180 <= hue < 240: + r,g,b = 0,x,c + elif 240 <= hue < 300: + r,g,b = x,0,c + elif 300 <= hue < 360: + r,g,b = c,0,x + + r = int((r + m) * 255) + g = int((g + m) * 255) + b = int((b + m) * 255) + + return r,g,b + + def rotHue(self, deg): + old = self._data + self._data = [[p for p in l ] for l in old] + for row in self._data: + for i,pixel in enumerate(row): + h,s,l = self._rgb2hsl(pixel) + h += deg + #TTkLog.debug(f"{h=}") + if h >= 360: h-=360 + row[i] = self._hsl2rgb((h,s,l)) + def paintEvent(self): img = self._data for y in range(0, len(img)&(~1), 2): diff --git a/demo/demo.py b/demo/demo.py index 8fc6aa69..6f7ef069 100755 --- a/demo/demo.py +++ b/demo/demo.py @@ -47,6 +47,7 @@ from showcase.tree import demoTree from showcase.fancytable import demoFancyTable from showcase.fancytree import demoFancyTree from showcase.textedit import demoTextEdit +from showcase.dragndrop import demoDnD words = ["Lorem", "ipsum", "dolor", "sit", "amet,", "consectetur", "adipiscing", "elit,", "sed", "do", "eiusmod", "tempor", "incididunt", "ut", "labore", "et", "dolore", "magna", "aliqua.", "Ut", "enim", "ad", "minim", "veniam,", "quis", "nostrud", "exercitation", "ullamco", "laboris", "nisi", "ut", "aliquip", "ex", "ea", "commodo", "consequat.", "Duis", "aute", "irure", "dolor", "in", "reprehenderit", "in", "voluptate", "velit", "esse", "cillum", "dolore", "eu", "fugiat", "nulla", "pariatur.", "Excepteur", "sint", "occaecat", "cupidatat", "non", "proident,", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollit", "anim", "id", "est", "laborum."] def getWord(): @@ -187,12 +188,16 @@ def demoShowcase(root=None, border=True): tabWindowsSources = [ 'showcase/windows.py' ] tabWindows.addMenu("[Sources]", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabWindowsSources[tabWindows.currentIndex()])) - listMenu.addItem(f"Area") + listMenu.addItem(f"Extra") tabArea = ttk.TTkTabWidget(parent=mainFrame, border=False, visible=False) tabArea.addTab(demoScrollArea(), " Scroll Area ") - tabAreaSources = [ 'showcase/scrollarea.py' ] + tabArea.addTab(demoDnD(), " Drag'n Drop ") + tabAreaSources = [ + 'showcase/scrollarea.py', + 'showcase/dragndrop.py' ] tabArea.addMenu("[Sources]", ttk.TTkK.RIGHT).menuButtonClicked.connect(lambda x : showSource(tabAreaSources[tabArea.currentIndex()])) + @ttk.pyTTkSlot(str) def _listCallback(label): widget = None @@ -203,7 +208,7 @@ def demoShowcase(root=None, border=True): elif label == "Pickers": widget = tabPickers elif label == "Graphs": widget = tabGraphs elif label == "Windows": widget = tabWindows - elif label == "Area": widget = tabArea + elif label == "Extra": widget = tabArea if widget: if _listCallback.active: _listCallback.active.hide() diff --git a/demo/showcase/dragndrop.py b/demo/showcase/dragndrop.py new file mode 100755 index 00000000..f46a7454 --- /dev/null +++ b/demo/showcase/dragndrop.py @@ -0,0 +1,97 @@ +#!/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 os +import sys +import random +import argparse + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +class DragThing(ttk.TTkFrame): + def __init__(self, *args, **kwargs): + ttk.TTkFrame.__init__(self, *args, **kwargs) + # Define and place 4 images with different Hue Color rotation + ttk.TTkImage(parent=self, pos=( 0, 0), data=ttk.TTkAbout.peppered) + ttk.TTkImage(parent=self, pos=( 0,10), data=ttk.TTkAbout.peppered).rotHue(60) + ttk.TTkImage(parent=self, pos=(15, 0), data=ttk.TTkAbout.peppered).rotHue(90) + ttk.TTkImage(parent=self, pos=(15,10), data=ttk.TTkAbout.peppered).rotHue(200) + self.setMaximumWidth(30) + self.setMinimumWidth(30) + + def mouseDragEvent(self, evt) -> bool: + ttk.TTkLog.debug("Start DnD") + drag = ttk.TTkDrag() + data = ttk.TTkImage(data=ttk.TTkAbout.peppered) + # Change color if the drag start over the side images, + # based on the same Hue rotation defined in the init + if evt.x <= 15 and evt.y > 10: data.rotHue(60) + elif evt.x > 15 and evt.y <= 10: data.rotHue(90) + elif evt.x > 15 and evt.y > 10: data.rotHue(200) + drag.setPixmap(data) + drag.setData(data) + drag.exec() + return True + +class DropThings(ttk.TTkFrame): + def dropEvent(self, evt) -> bool: + ttk.TTkLog.debug(f"Drop ({self.title()}) -> pos={evt.pos()}") + data = evt.data() + self.addWidget(data) + data.move(evt.x,evt.y) + self.update() + return True + +def demoDnD(root=None): + dndlayout = ttk.TTkGridLayout() + frame = ttk.TTkFrame(parent=root, layout=dndlayout, border=0) + dndlayout.addWidget(DragThing( title="Drag") ,0,0,2,1) + dndlayout.addWidget(DropThings(title="Drop 1"),0,1,1,2) + dndlayout.addWidget(DropThings(title="Drop 2"),0,3,1,1) + dndlayout.addWidget(DropThings(title="Drop 3"),1,1,1,1) + dndlayout.addWidget(DropThings(title="Drop 4"),1,2,1,2) + + # Add a debug window at the bottom to display the messages + dndlayout.addWidget(ttk.TTkLogViewer(follow=True),2,0,1,4) + return frame + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen', action='store_true') + args = parser.parse_args() + + ttk.TTkLog.use_default_file_logging() + + root = ttk.TTk() + if args.f: + rootTree = root + root.setLayout(ttk.TTkGridLayout()) + else: + rootTree = ttk.TTkWindow(parent=root,pos = (0,0), size=(70,40), title="Test Drag'n Drop", layout=ttk.TTkGridLayout(), border=True) + demoDnD(rootTree) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file