From ae8fc2ca3dd9be184fdcf0839cb033f16a6c6896 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Tue, 28 Mar 2023 18:44:14 +0100 Subject: [PATCH] added animation prototype --- TermTk/TTkCore/__init__.py | 3 +- TermTk/TTkCore/canvas.py | 6 - TermTk/TTkCore/propertyanimation.py | 129 ++++++++++++++++++ TermTk/TTkCore/ttk.py | 5 +- demo/showcase/animation.01.py | 141 ++++++++++++++++++++ tests/timeit/13.canvas.02.pushToTerminal.py | 29 +++- 6 files changed, 300 insertions(+), 13 deletions(-) create mode 100644 TermTk/TTkCore/propertyanimation.py create mode 100755 demo/showcase/animation.01.py diff --git a/TermTk/TTkCore/__init__.py b/TermTk/TTkCore/__init__.py index c23e4240..14593305 100644 --- a/TermTk/TTkCore/__init__.py +++ b/TermTk/TTkCore/__init__.py @@ -1,8 +1,9 @@ +from .signal import pyTTkSlot, pyTTkSignal from .log import TTkLog from .cfg import TTkCfg,TTkGlbl from .util import TTkUtil from .helper import TTkHelper -from .signal import pyTTkSlot, pyTTkSignal +from .propertyanimation import TTkPropertyAnimation from .ttk import TTk from .canvas import TTkCanvas from .color import TTkColor, TTkColorGradient, TTkLinearGradient diff --git a/TermTk/TTkCore/canvas.py b/TermTk/TTkCore/canvas.py index 2302f56a..674628d8 100644 --- a/TermTk/TTkCore/canvas.py +++ b/TermTk/TTkCore/canvas.py @@ -711,10 +711,6 @@ class TTkCanvas: lastcolor = TTkColor.RST empty = True ansi = "" - #for y in range(0, self._height): - # for x in range(0, self._width): - # if self._data[y][x] == oldData[y][x] and \ - # self._colors[y][x] == oldColors[y][x]: for y,(lda,ldb,lca,lcb) in enumerate(zip(data,oldData,colors,oldColors)): for x,(da,db,ca,cb) in enumerate(zip(lda,ldb,lca,lcb)): if da==db and ca==cb: @@ -722,8 +718,6 @@ class TTkCanvas: TTkTerm.push(ansi) empty=True continue - # ch = self._data[y][x] - # color = self._colors[y][x] ch = da color = ca if empty: diff --git a/TermTk/TTkCore/propertyanimation.py b/TermTk/TTkCore/propertyanimation.py new file mode 100644 index 00000000..dc6152c2 --- /dev/null +++ b/TermTk/TTkCore/propertyanimation.py @@ -0,0 +1,129 @@ +# 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 time, math + +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkCore.helper import TTkHelper + +class TTkEasingCurve(): + Linear = 0 + InQuad = 1 + OutQuad = 2 + InOutQuad = 3 + OutInQuad = 4 + InCubic = 5 + OutCubic = 6 + InOutCubic = 7 + OutInCubic = 8 + InQuart = 9 + OutQuart = 10 + InOutQuart = 11 + OutInQuart = 12 + InQuint = 13 + OutQuint = 14 + InOutQuint = 15 + OutInQuint = 16 + InSine = 17 + OutSine = 18 + InOutSine = 19 + OutInSine = 20 + InExpo = 21 + OutExpo = 22 + InOutExpo = 23 + OutInExpo = 24 + InCirc = 25 + OutCirc = 26 + InOutCirc = 27 + OutInCirc = 28 + InElastic = 29 + OutElastic = 30 + InOutElastic = 31 + OutInElastic = 32 + InBack = 33 + OutBack = 34 + InOutBack = 35 + OutInBack = 36 + InBounce = 37 + OutBounce = 38 + InOutBounce = 39 + OutInBounce = 40 + # BezierSpline = 45 + # TCBSpline = 46 + # Custom = 47 + +class TTkPropertyAnimation(): + __slots__ = ('_target', '_propertyName', '_parent', + '_duration', '_startValue', '_endValue', + '_easingCurve', '_baseTime') + def __init__(self, target, propertyName, parent=None): + self._target = target + self._propertyName = propertyName + self._parent = parent + self._duration = 0 + self._baseTime = 0 + self._startValue = None + self._endValue = None + + def setDuration(self, duration): + self._duration = duration + + def setStartValue(self, startValue): + self._startValue = startValue + + def setEndValue(self, endValue): + self._endValue = endValue + + def setEasingCurve(self, easingCurve): + self._easingCurve = easingCurve + + @pyTTkSlot() + def _refreshAnimation(self): + diff = time.time() - self._baseTime + if diff >= self._duration: + TTkHelper._rootWidget.paintExecuted.disconnect(self._refreshAnimation) + if type(self._endValue) in (list,tuple): + getattr(self._target,self._propertyName)(*self._endValue) + else: + getattr(self._target,self._propertyName)(self._endValue) + else: + def _processLinear(_s,_e,_v): + return int(_e*_v+_s*(1-_v)) + def _processQuad(_s,_e,_v): + return _processLinear(_s,_e,_v*_v) + def _processInQuad(_s,_e,_v): + return _processLinear(_s,_e,math.sqrt(math.sqrt(_v))) + _process = _processInQuad + v = diff/self._duration + if type(self._startValue) in (list,tuple): + newVal = [_process(s,e,v) for (s,e) in zip(self._startValue,self._endValue)] + getattr(self._target,self._propertyName)(*newVal) + else: + newVal = _process(self._startValue,self._endValue,v) + getattr(self._target,self._propertyName)(newVal) + + @pyTTkSlot() + def start(self): + self._baseTime = time.time() + if TTkHelper._rootWidget: + TTkHelper._rootWidget.paintExecuted.connect(self._refreshAnimation) + self._refreshAnimation() diff --git a/TermTk/TTkCore/ttk.py b/TermTk/TTkCore/ttk.py index a1b34816..8e8461e2 100644 --- a/TermTk/TTkCore/ttk.py +++ b/TermTk/TTkCore/ttk.py @@ -82,9 +82,11 @@ class TTk(TTkWidget): '_showMouseCursor', '_sigmask', '_drawMutex', - '_lastMultiTap') + '_lastMultiTap', + 'paintExecuted') def __init__(self, *args, **kwargs): + self.paintExecuted = pyTTkSignal() super().__init__(*args, **kwargs) self._termMouse = True self._termDirectMouse = kwargs.get('mouseTrack',False) @@ -251,6 +253,7 @@ class TTk(TTkWidget): self.setGeometry(0,0,w,h) self._fps() TTkHelper.paintAll() + self.paintExecuted.emit() self._drawMutex.release() self._timer.start(1/TTkCfg.maxFps) diff --git a/demo/showcase/animation.01.py b/demo/showcase/animation.01.py new file mode 100755 index 00000000..95996a24 --- /dev/null +++ b/demo/showcase/animation.01.py @@ -0,0 +1,141 @@ +#!/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 + +sys.path.append(os.path.join(sys.path[0],'..')) +from showcase._showcasehelper import getUtfColoredSentence + +class superSimpleHorizontalLine(ttk.TTkWidget): + def paintEvent(self): + w,h = self.size() + self._canvas.drawText(pos=(0,h-1), text='┕'+('━'*(w-2))+'ā”™',color=ttk.TTkColor.fg("#888888")) + + +def demoTextEditRO(root=None): + frame = ttk.TTkFrame(parent=root, border=False) + + winTe = ttk.TTkWindow(parent=frame, title="Text Edit", pos=(20,3), size=(50,30), layout=ttk.TTkGridLayout()) + te = ttk.TTkTextEdit(parent=winTe, lineNumber=True) + + winAc = ttk.TTkWindow(parent=frame, title="Animation Controls", pos=(0,0), size=(50,30)) + animBtn = ttk.TTkButton(parent=winAc, text="Animate",border=True,pos=(0,0)) + + anim = ttk.TTkPropertyAnimation(te.viewport(),'viewMoveTo') + anim.setDuration(1) + anim.setStartValue((0, 0)) + anim.setEndValue(( 50, 120)) + # anim.setEasingCurve(QEasingCurve.OutBounce) + + animBtn.clicked.connect(anim.start) + + # Initialize the textedit with come text + + te.setText(ttk.TTkString("Text Edit DEMO\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD+ttk.TTkColor.ITALIC)) + + # Load ANSI input + te.append(ttk.TTkString("ANSI Input Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + with open(os.path.join(os.path.dirname(os.path.abspath(__file__)),'textedit.ANSI.txt')) as f: + te.append(f.read()) + + # Test Variable sized chars + te.append(ttk.TTkString("Test Variable sized chars\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + te.append( "Emoticons: -šŸ˜šŸ˜‚šŸ˜šŸ˜Ž----") + te.append( " --šŸ˜šŸ˜šŸ˜‚šŸ˜šŸ˜Ž-") + te.append("") + + te.append( " UTF-8: Ā£ @ Ā£ ¬ ` ę¼¢ _ _ 恂 _ _") + te.append( " |.|.|.|.|.||.|.|.||.|.|.") + te.append("") + + + zc1 = chr(0x07a6) + zc2 = chr(0x20D7) + zc3 = chr(0x065f) + te.append( " - | | | | | -") + te.append(f"Zero Size: - o{zc1} o{zc2} o{zc3} o{zc1}{zc2} o{zc1}{zc2}{zc3} -") + te.append( " - | | | | | -") + te.append("") + + te.append(f"Plus Tabs: -\t😁\tšŸ˜\to{zc1}{zc2}{zc3}\tšŸ˜Ž\to{zc1}{zc2}{zc3}\tšŸ˜‚-") + te.append("") + + # Test Tabs + te.append(ttk.TTkString("Tabs Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + te.append("Word\tAnother Word\tYet more words") + te.append("What a wonderful word\tOut of this word\tBattle of the words\tThe city of thousand words\tThe word is not enough\tJurassic word\n") + te.append("tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("-tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("--tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("---tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("----tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("-----tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("------tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab") + te.append("-------tab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\ttab\n") + + te.append(ttk.TTkString("Random TTkString Input Test\n",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + te.append(ttk.TTkString('\n').join([ getUtfColoredSentence(3,10) for _ in range(100)])) + + te.append(ttk.TTkString("-- The Very END --",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) + + # use the widget size to wrap + # te.setLineWrapMode(ttk.TTkK.WidgetWidth) + # te.setWordWrapMode(ttk.TTkK.WordWrap) + + # Use a fixed wrap size + # te.setLineWrapMode(ttk.TTkK.FixedWidth) + # te.setWrapWidth(100) + + + + + + return frame + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('-f', help='Full Screen (default)', action='store_true') + parser.add_argument('-w', help='Windowed', action='store_true') + args = parser.parse_args() + windowed = args.w + + ttk.TTkLog.use_default_file_logging() + + root = ttk.TTk() + if windowed: + rootTree = ttk.TTkWindow(parent=root,pos = (0,0), size=(70,40), title="Test Text Edit", layout=ttk.TTkGridLayout(), border=True) + else: + rootTree = root + root.setLayout(ttk.TTkGridLayout()) + demoTextEditRO(rootTree) + root.mainloop() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tests/timeit/13.canvas.02.pushToTerminal.py b/tests/timeit/13.canvas.02.pushToTerminal.py index f691e59b..846966ab 100644 --- a/tests/timeit/13.canvas.02.pushToTerminal.py +++ b/tests/timeit/13.canvas.02.pushToTerminal.py @@ -298,7 +298,7 @@ c6 = { # dataA,colorsA = c1['data'], c1['colors'] # dataB,colorsB = c2['data'], c2['colors'] -dataA,colorsA = c3['data'], c3['colors'] +dataA,colorsA = c5['data'], c5['colors'] dataB,colorsB = c6['data'], c6['colors'] w=len(dataA[0]) @@ -348,8 +348,7 @@ def ptt2(): if not empty: empty=True continue - ch = da - color = ca + ch, color = da, ca if empty: ansi = ttk.TTkTerm.Cursor.moveTo(y+1,x+1) empty = False @@ -360,11 +359,31 @@ def ptt2(): if not empty: empty=True - +def ptt3(): + oldData, oldColors = dataB, colorsB + lastcolor = ttk.TTkColor.RST + empty = True + ansi = "" + for y,(lda,ldb,lca,lcb) in enumerate(zip(dataA,dataB,colorsA,colorsB)): + for x,(da,db,ca,cb) in enumerate(zip(lda,ldb,lca,lcb)): + if da==db and ca==cb: + if not empty: + empty=True + continue + ch, color = da, ca + if empty: + ansi = ttk.TTkTerm.Cursor.moveTo(y+1,x+1) + empty = False + if color != lastcolor: + ansi += color.canvasDiff2Str(lastcolor) + lastcolor = color + ansi+=ch + if not empty: + empty=True def test1(): return ptt1() def test2(): return ptt2() -def test3(): return ptt1() +def test3(): return ptt2() def test4(): return ptt1() def test5(): return ptt1() def test6(): return ptt1()