diff --git a/TermTk/TTkAbstract/abstractscrollview.py b/TermTk/TTkAbstract/abstractscrollview.py index a6a2f654..d3f6831c 100644 --- a/TermTk/TTkAbstract/abstractscrollview.py +++ b/TermTk/TTkAbstract/abstractscrollview.py @@ -38,7 +38,7 @@ class TTkAbstractScrollViewInterface(): raise NotImplementedError() @pyTTkSlot(int, int) - def viewMoveTo(self, x, y): + def viewMoveTo(self, x: int, y: int): raise NotImplementedError() def getViewOffsets(self): @@ -60,7 +60,7 @@ class TTkAbstractScrollView(TTkWidget, TTkAbstractScrollViewInterface): self._viewOffsetY = 0 @pyTTkSlot(int, int) - def viewMoveTo(self, x, y): + def viewMoveTo(self, x: int, y: int): fw, fh = self.viewFullAreaSize() dw, dh = self.viewDisplayedSize() rangex = fw - dw @@ -113,7 +113,7 @@ class TTkAbstractScrollViewGridLayout(TTkGridLayout, TTkAbstractScrollViewInterf self._excludeEvent = False @pyTTkSlot(int, int) - def viewMoveTo(self, x, y): + def viewMoveTo(self, x: int, y: int): fw, fh = self.viewFullAreaSize() dw, dh = self.viewDisplayedSize() rangex = fw - dw diff --git a/TermTk/TTkCore/propertyanimation.py b/TermTk/TTkCore/propertyanimation.py index dae634d9..1a263445 100644 --- a/TermTk/TTkCore/propertyanimation.py +++ b/TermTk/TTkCore/propertyanimation.py @@ -21,7 +21,10 @@ # SOFTWARE. import time, math +from inspect import getfullargspec +from types import LambdaType +from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkCore.helper import TTkHelper @@ -127,7 +130,7 @@ class TTkEasingCurve(): return n1 * x * x + 0.984375 class TTkPropertyAnimation(): - __slots__ = ('_target', '_propertyName', '_parent', + __slots__ = ('_target', '_propertyName', '_parent', '_cb', '_cast', '_duration', '_startValue', '_endValue', '_easingCurve', '_baseTime') def __init__(self, target, propertyName, parent=None): @@ -140,6 +143,25 @@ class TTkPropertyAnimation(): self._endValue = None self._easingCurve = TTkEasingCurve(TTkEasingCurve.Linear) + if type(propertyName) == str: + self._cb = getattr(self._target,self._propertyName) + else: + self._cb = propertyName + + def _cast(): + _spec = getfullargspec(self._cb) + if isinstance(self._cb, LambdaType) and self._cb.__name__ == "": + _args = _spec.args + else: + _args = _spec.args[1:] if hasattr(self._cb, '__self__') else _spec.args + _castList = [ + (lambda x:_spec.annotations[a](x)) if a in _spec.annotations else (lambda x:x) for a in _args] + def _ret(*args): + return [c(x) for (c,x) in zip(_castList, args)] + return _ret + + self._cast = _cast() + def setDuration(self, duration): self._duration = duration @@ -155,20 +177,21 @@ class TTkPropertyAnimation(): @pyTTkSlot() def _refreshAnimation(self): diff = time.time() - self._baseTime + # TTkLog.info(f"diff: {diff}") if diff >= self._duration: TTkHelper._rootWidget.paintExecuted.disconnect(self._refreshAnimation) if type(self._endValue) in (list,tuple): - getattr(self._target,self._propertyName)(*self._endValue) + self._cb(*self._cast(*self._endValue)) else: - getattr(self._target,self._propertyName)(self._endValue) + self._cb(*self._cast(self._endValue)) else: v = diff/self._duration if type(self._startValue) in (list,tuple): - newVal = [int(self._easingCurve.process(s,e,v)) for (s,e) in zip(self._startValue,self._endValue)] - getattr(self._target,self._propertyName)(*newVal) + newVal = [self._easingCurve.process(s,e,v) for (s,e) in zip(self._startValue,self._endValue)] + self._cb(*self._cast(*newVal)) else: - newVal = int(self._easingCurve.process(self._startValue,self._endValue,v)) - getattr(self._target,self._propertyName)(newVal) + newVal = self._easingCurve.process(self._startValue,self._endValue,v) + self._cb(*self._cast(newVal)) @pyTTkSlot() def start(self): diff --git a/TermTk/TTkTestWidgets/keypressview.py b/TermTk/TTkTestWidgets/keypressview.py index 036c9fbb..f06bf1d8 100644 --- a/TermTk/TTkTestWidgets/keypressview.py +++ b/TermTk/TTkTestWidgets/keypressview.py @@ -27,24 +27,21 @@ from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.signal import pyTTkSlot from TermTk.TTkCore.constant import TTkK -from TermTk.TTkCore.timer import TTkTimer +from TermTk.TTkCore.propertyanimation import TTkPropertyAnimation, TTkEasingCurve from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.widget import TTkWidget from TermTk.TTkTestWidgets.keypressviewfont import TTkKeyPressViewFont class TTkKeyPressView(TTkWidget): - __slots__ = ('_timer','_keys','_fade','_period') + __slots__ = ('_fadeDuration','_keys','_anim') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) TTkHelper._rootWidget._input.inputEvent.connect(self._processInput) self._keys = [] - self._period = 0.1 - self._fade = 10 - self._timer = TTkTimer() - self._timer.timeout.connect(self._timerEvent) - self._timer.start(self._period) + self._fadeDuration = 2.5 + self._anim = TTkPropertyAnimation(self, '_pushFade') @pyTTkSlot(TTkKeyEvent, TTkMouseEvent) def _processInput(self, kevt, mevt): @@ -62,10 +59,10 @@ class TTkKeyPressView(TTkWidget): text = f"{m} {text}" if self._keys and evt.type == self._keys[-1][2] == TTkK.Character: self._keys[-1][1]+=evt.key - self._keys[-1][0]=0 + self._keys[-1][0]=1 else: - self._keys.append([0,text,evt.type]) - self.update() + self._keys.append([1,text,evt.type]) + self._startFade() @pyTTkSlot(TTkMouseEvent) def _addMouse(self, evt): @@ -77,17 +74,22 @@ class TTkKeyPressView(TTkWidget): if evt.tap>3: tap=f" {evt.tap} Clicks " text = f"M:{(evt.x,evt.y)} {evt.key2str().replace('Button','')}{tap}{evt.mod2str().replace('NoModifier','')}" - self._keys.append([0,text,0x100]) - self.update() + self._keys.append([1,text,0x100]) + self._startFade() - def _timerEvent(self): + def _startFade(self): + self._anim.setDuration(self._fadeDuration) + self._anim.setStartValue(0) + self._anim.setEndValue(1) + self._anim.setEasingCurve(TTkEasingCurve.OutExpo) + self._anim.start() + + def _pushFade(self, fade: float): for i,k in enumerate(self._keys): - if k[0] > self._fade: + k[0] -= fade + if k[0] <= 0: self._keys.pop(i) - else: - k[0] += 1 - self.update() - self._timer.start(self._period) + self.update() def txt2map(self, txt): ret = ["","",""] @@ -99,12 +101,10 @@ class TTkKeyPressView(TTkWidget): return ret def paintEvent(self): - for k in self._keys: - text = k[1] - gr = 0x1000 - 0x1000 * k[0] // self._fade - r = 0xbb*gr//0x1000 - g = 0xff*gr//0x1000 - b = 0xff*gr//0x1000 + for alpha,text,_ in self._keys: + r = int(0xbb*alpha) + g = int(0xff*alpha) + b = int(0xff*alpha) color = TTkColor.fg(f"#{r<<16|g<<8|b:06x}") #self._canvas.drawText(pos=((self.width()-len(text))//2,0),text=text,color=color) m = self.txt2map(text) diff --git a/TermTk/TTkWidgets/Fancy/tableview.py b/TermTk/TTkWidgets/Fancy/tableview.py index 84a39985..7f2dbda5 100644 --- a/TermTk/TTkWidgets/Fancy/tableview.py +++ b/TermTk/TTkWidgets/Fancy/tableview.py @@ -221,7 +221,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): self.viewChanged.emit() self.update() - def insertItem(self, index, item, id=None): + def insertItem(self, index: int, item, id=None): if len(item) != len(self._columns): return# textItem = [TTkString(i) if isinstance(i,str) else i if issubclass(type(i), TTkString) else TTkString() for i in item] @@ -243,7 +243,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): self.viewChanged.emit() self.update() - def removeItemAt(self, index): + def removeItemAt(self, index: int): if self._selected == index: self._selected = -1 del self._tableDataId[index] @@ -253,7 +253,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): self.viewChanged.emit() self.update() - def removeItemsFrom(self, index): + def removeItemsFrom(self, index: int): if self._selected >= index: self._selected = -1 self._tableDataId = self._tableDataId[:index] @@ -263,7 +263,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): self.viewChanged.emit() self.update() - def itemAt(self, index): + def itemAt(self, index: int): if 0 <= index < len(self._tableDataId): if item:=self._tableDataWidget[index]: return item @@ -271,7 +271,7 @@ class _TTkFancyTableView(TTkAbstractScrollView): return self._tableDataText[index] return None - def dataAt(self, index): + def dataAt(self, index: int): if 0 <= index < len(self._tableDataId): return self._tableDataId[index] return None @@ -392,7 +392,7 @@ class TTkFancyTableView(TTkAbstractScrollView): self.removeItemsFrom = self._tableView.removeItemsFrom @pyTTkSlot(int, int) - def viewMoveTo(self, x, y): + def viewMoveTo(self, x: int, y: int): self._tableView.viewMoveTo(x, y) def getViewOffsets(self): diff --git a/TermTk/TTkWidgets/widget.py b/TermTk/TTkWidgets/widget.py index 89f6ec98..79b2092f 100644 --- a/TermTk/TTkWidgets/widget.py +++ b/TermTk/TTkWidgets/widget.py @@ -274,7 +274,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): ''' Event Callback triggered after a successful resize''' pass - def setDefaultSize(self, arg, width, height): + def setDefaultSize(self, arg, width: int, height: int): if ( 'size' in arg or 'width' in arg or 'height' in arg ): @@ -327,7 +327,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): ''' return self._padt, self._padb, self._padl, self._padr - def setPadding(self, top, bottom, left, right): + def setPadding(self, top: int, bottom: int, left: int, right: int): ''' Set the padding of the widget :param int top: top padding @@ -544,26 +544,26 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return lMinW return wMinW - def setMaximumSize(self, maxw, maxh): + def setMaximumSize(self, maxw: int, maxh: int): self.setMaximumWidth(maxw) self.setMaximumHeight(maxh) - def setMaximumHeight(self, maxh): + def setMaximumHeight(self, maxh: int): if self._maxh == maxh: return self._maxh = maxh self.update(updateLayout=True, updateParent=True) - def setMaximumWidth(self, maxw): + def setMaximumWidth(self, maxw: int): if self._maxw == maxw: return self._maxw = maxw self.update(updateLayout=True, updateParent=True) - def setMinimumSize(self, minw, minh): + def setMinimumSize(self, minw: int, minh: int): self.setMinimumWidth(minw) self.setMinimumHeight(minh) - def setMinimumHeight(self, minh): + def setMinimumHeight(self, minh: int): if self._minh == minh: return self._minh = minh self.update(updateLayout=True, updateParent=True) - def setMinimumWidth(self, minw): + def setMinimumWidth(self, minw: int): if self._minw == minw: return self._minw = minw self.update(updateLayout=True, updateParent=True) @@ -609,7 +609,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.hide() @pyTTkSlot(bool) - def setVisible(self, visible): + def setVisible(self, visible: bool): if visible: self.show() else: self.hide() @@ -625,7 +625,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): # TODO: Remove This def layoutUpdated(self): pass - def update(self, repaint=True, updateLayout=False, updateParent=False): + def update(self, repaint: bool =True, updateLayout: bool =False, updateParent: bool =False): if repaint: TTkHelper.addUpdateBuffer(self) TTkHelper.addUpdateWidget(self) @@ -686,7 +686,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): return self._enabled @pyTTkSlot(bool) - def setEnabled(self, enabled=True): + def setEnabled(self, enabled: bool=True): if self._enabled == enabled: return self._enabled = enabled self.update() @@ -709,10 +709,10 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): def toolTip(self): return self._toolTip - def setToolTip(self, toolTip): + def setToolTip(self, toolTip: TTkString): self._toolTip = toolTip - def getWidgetByName(self, name): + def getWidgetByName(self, name: str): for w in self.rootLayout().iterWidgets(onlyVisible=False, recurse=True): if w._name == name: return w diff --git a/demo/showcase/animation.01.py b/demo/showcase/animation.01.py index c99b4f40..4e15c5b0 100755 --- a/demo/showcase/animation.01.py +++ b/demo/showcase/animation.01.py @@ -40,22 +40,97 @@ class superSimpleHorizontalLine(ttk.TTkWidget): def demoTextEditRO(root=None): + easingList = ( + (ttk.TTkEasingCurve.Linear , 'Linear'), + (ttk.TTkEasingCurve.InQuad , 'InQuad'), + (ttk.TTkEasingCurve.OutQuad , 'OutQuad'), + (ttk.TTkEasingCurve.InOutQuad , 'InOutQuad'), + (ttk.TTkEasingCurve.OutInQuad , 'OutInQuad'), + (ttk.TTkEasingCurve.InCubic , 'InCubic'), + (ttk.TTkEasingCurve.OutCubic , 'OutCubic'), + (ttk.TTkEasingCurve.InOutCubic , 'InOutCubic'), + (ttk.TTkEasingCurve.OutInCubic , 'OutInCubic'), + (ttk.TTkEasingCurve.InQuart , 'InQuart'), + (ttk.TTkEasingCurve.OutQuart , 'OutQuart'), + (ttk.TTkEasingCurve.InOutQuart , 'InOutQuart'), + (ttk.TTkEasingCurve.OutInQuart , 'OutInQuart'), + (ttk.TTkEasingCurve.InQuint , 'InQuint'), + (ttk.TTkEasingCurve.OutQuint , 'OutQuint'), + (ttk.TTkEasingCurve.InOutQuint , 'InOutQuint'), + (ttk.TTkEasingCurve.OutInQuint , 'OutInQuint'), + (ttk.TTkEasingCurve.InSine , 'InSine'), + (ttk.TTkEasingCurve.OutSine , 'OutSine'), + (ttk.TTkEasingCurve.InOutSine , 'InOutSine'), + (ttk.TTkEasingCurve.OutInSine , 'OutInSine'), + (ttk.TTkEasingCurve.InExpo , 'InExpo'), + (ttk.TTkEasingCurve.OutExpo , 'OutExpo'), + (ttk.TTkEasingCurve.InOutExpo , 'InOutExpo'), + (ttk.TTkEasingCurve.OutInExpo , 'OutInExpo'), + (ttk.TTkEasingCurve.InCirc , 'InCirc'), + (ttk.TTkEasingCurve.OutCirc , 'OutCirc'), + (ttk.TTkEasingCurve.InOutCirc , 'InOutCirc'), + (ttk.TTkEasingCurve.OutInCirc , 'OutInCirc'), + (ttk.TTkEasingCurve.InElastic , 'InElastic'), + (ttk.TTkEasingCurve.OutElastic , 'OutElastic'), + (ttk.TTkEasingCurve.InOutElastic , 'InOutElastic'), + (ttk.TTkEasingCurve.OutInElastic , 'OutInElastic'), + (ttk.TTkEasingCurve.InBack , 'InBack'), + (ttk.TTkEasingCurve.OutBack , 'OutBack'), + (ttk.TTkEasingCurve.InOutBack , 'InOutBack'), + (ttk.TTkEasingCurve.OutInBack , 'OutInBack'), + (ttk.TTkEasingCurve.InBounce , 'InBounce'), + (ttk.TTkEasingCurve.OutBounce , 'OutBounce'), + (ttk.TTkEasingCurve.InOutBounce , 'InOutBounce'), + (ttk.TTkEasingCurve.OutInBounce , 'OutInBounce')) + 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(( 00, 70)) - # anim.setEasingCurve(ttk.TTkEasingCurve.OutQuad) - anim.setEasingCurve(ttk.TTkEasingCurve.OutBounce) - - animBtn.clicked.connect(anim.start) + animBtnScroll = ttk.TTkButton(parent=winAc, text="Anim Scroll",border=True,pos=(0,0)) + animBtnWinPos = ttk.TTkButton(parent=winAc, text="Anim Pos",border=True,pos=(15,0)) + animBoth = ttk.TTkButton(parent=winAc, text="Anim Both",border=True,pos=(25,0)) + + class PosControls(ttk.TTkFrame): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs|{'border':True}) + ttk.TTkLabel( parent=self,pos=(0,0),text='Starting Position (x,y)') + self.axspb = ttk.TTkSpinBox(parent=self,maximum=200, minimum=-100, pos=(0,1),size=(9,1),value=0) + self.ayspb = ttk.TTkSpinBox(parent=self,maximum=200, minimum=-100, pos=(8,1),size=(9,1),value=100) + ttk.TTkLabel( parent=self,pos=(0,2),text='Ending Position (x,y)') + self.bxspb = ttk.TTkSpinBox(parent=self,maximum=200, minimum=-100, pos=(0,3),size=(9,1),value=0) + self.byspb = ttk.TTkSpinBox(parent=self,maximum=200, minimum=-100, pos=(8,3),size=(9,1),value=0) + ttk.TTkLabel( parent=self,pos=(0,4),text='Duration (sec.)') + self.dursb = ttk.TTkSpinBox(parent=self,maximum=200, minimum=0, pos=(0,5),size=(12,1),value=2) + ttk.TTkLabel( parent=self,pos=(0,6),text='Easing Curve') + self.ecb = ttk.TTkComboBox(parent=self,pos=(0,7),size=(20,1),list=[v for (_,v) in easingList],index=0) + + pcScroll = PosControls(parent=winAc, pos=(0,3), size=(25,10), title="Text Scroll") + pcWinPos = PosControls(parent=winAc, pos=(0,13), size=(25,10), title="Window Position") + + + animScroll = ttk.TTkPropertyAnimation(te.viewport(),'viewMoveTo') + animWinPos = ttk.TTkPropertyAnimation(None, winTe.move) + + def _startAnimScroll(): + animScroll.setDuration(pcScroll.dursb.value()) + animScroll.setStartValue((pcScroll.axspb.value(), pcScroll.ayspb.value())) + animScroll.setEndValue( (pcScroll.bxspb.value(), pcScroll.byspb.value())) + animScroll.setEasingCurve({t:v for (v,t) in easingList}.get(pcScroll.ecb.currentText(),easingList[0][0])) + animScroll.start() + def _startAnimWinPos(): + animWinPos.setDuration(pcWinPos.dursb.value()) + animWinPos.setStartValue((pcWinPos.axspb.value(), pcWinPos.ayspb.value())) + animWinPos.setEndValue( (pcWinPos.bxspb.value(), pcWinPos.byspb.value())) + animWinPos.setEasingCurve({t:v for (v,t) in easingList}.get(pcWinPos.ecb.currentText(),easingList[0][0])) + animWinPos.start() + + animBtnScroll.clicked.connect(_startAnimScroll) + animBtnWinPos.clicked.connect(_startAnimWinPos) + animBoth.clicked.connect(_startAnimScroll) + animBoth.clicked.connect(_startAnimWinPos) # Initialize the textedit with come text @@ -102,7 +177,7 @@ def demoTextEditRO(root=None): 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('\n').join([ getUtfColoredSentence(10,15) for _ in range(100)])) te.append(ttk.TTkString("-- The Very END --",ttk.TTkColor.UNDERLINE+ttk.TTkColor.BOLD)) diff --git a/tests/test.argspec.001.py b/tests/test.argspec.001.py new file mode 100644 index 00000000..b6c8849b --- /dev/null +++ b/tests/test.argspec.001.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 + +# 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 inspect import getfullargspec + +def f1(a:int, b:float =123.123, c:str = "pippo", d=1234) -> str: + return f"{a=} {b=} {c=}" + +spec = getfullargspec(f1) + +print(f"{spec=}") + +x = spec.annotations['a'](1.001) +y = spec.annotations['b'](1.001) +z = spec.annotations['c'](1.001) + +print(f"{x=} {y=} {z=}")