Source code for TermTk.TTkCore.propertyanimation

# MIT License
#
# Copyright (c) 2023 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.

__all__ = ['TTkPropertyAnimation', 'TTkEasingCurve']

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

[docs] 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 __slots__ = ('_easingFunc') def __init__(self, easingCurve=Linear): self._easingFunc = {self.Linear : TTkEasingCurve._ecLinear, self.InQuad : TTkEasingCurve._ecInQuad, self.OutQuad : TTkEasingCurve._ecOutQuad, self.InOutQuad : TTkEasingCurve._ecInOutQuad, self.OutInQuad : TTkEasingCurve._ecOutInQuad, self.InCubic : TTkEasingCurve._ecInCubic, self.OutCubic : TTkEasingCurve._ecOutCubic, self.InOutCubic : TTkEasingCurve._ecInOutCubic, self.OutInCubic : TTkEasingCurve._ecOutInCubic, self.InQuart : TTkEasingCurve._ecInQuart, self.OutQuart : TTkEasingCurve._ecOutQuart, self.InOutQuart : TTkEasingCurve._ecInOutQuart, self.OutInQuart : TTkEasingCurve._ecOutInQuart, self.InQuint : TTkEasingCurve._ecInQuint, self.OutQuint : TTkEasingCurve._ecOutQuint, self.InOutQuint : TTkEasingCurve._ecInOutQuint, self.OutInQuint : TTkEasingCurve._ecOutInQuint, self.InSine : TTkEasingCurve._ecInSine, self.OutSine : TTkEasingCurve._ecOutSine, self.InOutSine : TTkEasingCurve._ecInOutSine, self.OutInSine : TTkEasingCurve._ecOutInSine, self.InExpo : TTkEasingCurve._ecInExpo, self.OutExpo : TTkEasingCurve._ecOutExpo, self.InOutExpo : TTkEasingCurve._ecInOutExpo, self.OutInExpo : TTkEasingCurve._ecOutInExpo, self.InCirc : TTkEasingCurve._ecInCirc, self.OutCirc : TTkEasingCurve._ecOutCirc, self.InOutCirc : TTkEasingCurve._ecInOutCirc, self.OutInCirc : TTkEasingCurve._ecOutInCirc, self.InElastic : TTkEasingCurve._ecInElastic, self.OutElastic : TTkEasingCurve._ecOutElastic, self.InOutElastic : TTkEasingCurve._ecInOutElastic, self.OutInElastic : TTkEasingCurve._ecOutInElastic, self.InBack : TTkEasingCurve._ecInBack, self.OutBack : TTkEasingCurve._ecOutBack, self.InOutBack : TTkEasingCurve._ecInOutBack, self.OutInBack : TTkEasingCurve._ecOutInBack, self.InBounce : TTkEasingCurve._ecInBounce, self.OutBounce : TTkEasingCurve._ecOutBounce, self.InOutBounce : TTkEasingCurve._ecInOutBounce, self.OutInBounce : TTkEasingCurve._ecOutInBounce, self.OutBounce : TTkEasingCurve._ecOutBounce, }.get(easingCurve,TTkEasingCurve._ecLinear)
[docs] def process(self,a,b,v): v = self._easingFunc(v) return float(b)*v+float(a)*(1-v)
@staticmethod def _ecMixCbHelper(incb,outcb,v): if v < 0.5: return incb(2*v)/2 return 0.5+outcb(2*v-1)/2 @staticmethod def _ecInOutHelper(incb,outcb,v): if v < 0.5: return incb(2*v)/2 return 0.5+outcb(2*v-1)/2 @staticmethod def _ecOutInHelper(incb,outcb,v): if v < 0.5: return outcb(2*v)/2 return 0.5+incb(2*v-1)/2 # Equations adapted from # - https://easings.net # - https://github.com/qt/qtbase/blob/dev/src/3rdparty/easing/easing.cpp @staticmethod def _ecLinear(v): return v @staticmethod def _ecInQuad(v): return v*v @staticmethod def _ecOutQuad(v): return -v*(v-2) @staticmethod def _ecInOutQuad(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInQuad, TTkEasingCurve._ecOutQuad, v) @staticmethod def _ecOutInQuad(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutQuad, TTkEasingCurve._ecInQuad, v) @staticmethod def _ecInCubic(v): return v*v*v @staticmethod def _ecOutCubic(v): v-=1 ; return 1+v*v*v @staticmethod def _ecInOutCubic(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInCubic, TTkEasingCurve._ecOutCubic, v) @staticmethod def _ecOutInCubic(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutCubic, TTkEasingCurve._ecInCubic, v) @staticmethod def _ecInQuart(v): return v*v*v*v @staticmethod def _ecOutQuart(v): v-=1 ; return 1-v*v*v*v @staticmethod def _ecInOutQuart(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInQuart, TTkEasingCurve._ecOutQuart, v) @staticmethod def _ecOutInQuart(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutQuart, TTkEasingCurve._ecInQuart, v) @staticmethod def _ecInQuint(v): return v*v*v*v*v @staticmethod def _ecOutQuint(v): v-=1 ; return 1+v*v*v*v*v @staticmethod def _ecInOutQuint(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInQuint, TTkEasingCurve._ecOutQuint, v) @staticmethod def _ecOutInQuint(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutQuint, TTkEasingCurve._ecInQuint, v) @staticmethod def _ecInSine(v): return 1-math.sin(math.pi*(1-v)/2) @staticmethod def _ecOutSine(v): return math.sin(math.pi*v/2) @staticmethod def _ecInOutSine(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInSine, TTkEasingCurve._ecOutSine, v) @staticmethod def _ecOutInSine(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutSine, TTkEasingCurve._ecInSine, v) @staticmethod def _ecInExpo(v): return v if v==1 or v==0 else math.pow(2,10*(v-1))-0.001 @staticmethod def _ecOutExpo(v): return 1 if v==1 else 1.001*(1-math.pow(2,-10*v)) @staticmethod def _ecInOutExpo(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInExpo, TTkEasingCurve._ecOutExpo, v) @staticmethod def _ecOutInExpo(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutExpo, TTkEasingCurve._ecInExpo, v) @staticmethod def _ecInCirc(v): return 1-math.sqrt(1-v*v) @staticmethod def _ecOutCirc(v): v-=1 ; return math.sqrt(1-v*v) @staticmethod def _ecInOutCirc(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInCirc, TTkEasingCurve._ecOutCirc, v) @staticmethod def _ecOutInCirc(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutCirc, TTkEasingCurve._ecInCirc, v) @staticmethod def _ecInElastic(v): return v if v in [0,1] else -math.pow(2, 10 *v-10)*math.sin((v*10-10.75)*(2*math.pi)/3) @staticmethod def _ecOutElastic(v): return 1-TTkEasingCurve._ecInElastic(1-v) @staticmethod def _ecInOutElastic(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInElastic, TTkEasingCurve._ecOutElastic, v) @staticmethod def _ecOutInElastic(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutElastic, TTkEasingCurve._ecInElastic, v) @staticmethod def _ecInBack(v,s=1.7): return v*v*((s+1)*v-s) @staticmethod def _ecOutBack(v,s=1.7): v-=1 ; return v*v*((s+1)*v+s)+1 @staticmethod def _ecInOutBack(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInBack, TTkEasingCurve._ecOutBack, v) @staticmethod def _ecOutInBack(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutBack, TTkEasingCurve._ecInBack, v) @staticmethod def _ecInBounce(v): return 1-TTkEasingCurve._ecOutBounce(1-v) @staticmethod def _ecOutBounce(x): n1 = 7.5625 d1 = 2.75 if x < 1 / d1: return n1 * x * x elif x < 2 / d1: x -= 1.5 / d1 return n1 * x * x + 0.75 elif x < 2.5 / d1: x -= 2.25 / d1 return n1 * x * x + 0.9375 else: x -= 2.625 / d1 return n1 * x * x + 0.984375 @staticmethod def _ecInOutBounce(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecInBounce, TTkEasingCurve._ecOutBounce, v) @staticmethod def _ecOutInBounce(v): return TTkEasingCurve._ecMixCbHelper( TTkEasingCurve._ecOutBounce, TTkEasingCurve._ecInBounce, v)
[docs] class TTkPropertyAnimation(): __slots__ = ('_target', '_propertyName', '_parent', '_cb', '_cast', '_duration', '_startValue', '_endValue', '_easingCurve', '_baseTime', # Signals 'finished') def __init__(self, target, propertyName, parent=None): self.finished = pyTTkSignal() self._target = target self._propertyName = propertyName self._parent = parent self._duration = 0 self._baseTime = 0 self._startValue = None 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__ == "<lambda>": _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()
[docs] def setDuration(self, duration): self._duration = duration
[docs] def setStartValue(self, startValue): self._startValue = startValue
[docs] def setEndValue(self, endValue): self._endValue = endValue
[docs] def setEasingCurve(self, easingCurve): self._easingCurve = TTkEasingCurve(easingCurve)
@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): self._cb(*self._cast(*self._endValue)) else: self._cb(*self._cast(self._endValue)) self.finished.emit() else: v = diff/self._duration if type(self._startValue) in (list,tuple): newVal = [self._easingCurve.process(s,e,v) for (s,e) in zip(self._startValue,self._endValue)] self._cb(*self._cast(*newVal)) else: newVal = self._easingCurve.process(self._startValue,self._endValue,v) self._cb(*self._cast(newVal)) TTkHelper.unlockPaint()
[docs] @pyTTkSlot() def start(self): self._baseTime = time.time() if TTkHelper._rootWidget: TTkHelper._rootWidget.paintExecuted.connect(self._refreshAnimation) self._refreshAnimation()