Closed Guendo345 closed 1 year ago
Give it a try - https://github.com/VasigaranAndAngel/PyAnimator
the sad thing is CTk is not smooth for this.
It's not bad but the idea is to integrate directly into Custom Tkinter to add dynamism to elements like smooth move or when a frame dimension is (width=100, height=100) when there is a change of dimension (width=200, height=200) that it is slower
Tried .moveto
in canvas.
If we animate this slower, it's not smooth. I don't know if we can go more deeper and animate the elements.
so, adding dynamism to widgets, not will smoother.
try with this code pls
import customtkinter
class smooth_frame(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("smooth frame")
self.maxsize(600, 500)
self.minsize(600, 500)
self.frame = customtkinter.CTkFrame(self, width=200, height=200, fg_color='black')
self.frame.place(x=10, y=10)
self.button = customtkinter.CTkButton(self, text="Smooth resize frame")
self.button.place(x=10, y=350)
if __name__ == "__main__":
app = smooth_frame()
app.overrideredirect(False)
app.mainloop()
if we look at this closer, it's not moving in some values for some reason.
can you show me the code
def _resize_frame(self):
speed = 1
animator = Animator(200, 480, 1*speed, 60//speed, 'ease')
st = time.perf_counter()
for value in animator:
self.frame.configure(height=value, width=value)
self.update()
en = time.perf_counter()
self.label1.configure(text='rounded value - ' + str(round(value)))
self.label2.configure(text='direct value - ' + str(value))
self.label3.configure(text='fps - ' + str(1/(en-st)))
self.update()
st = time.perf_counter()
full code because you add label pls
and i want to know how to set limit for frame size
import customtkinter
from PyAnimator import Animator
import time
class smooth_frame(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("smooth frame")
self.maxsize(600, 500)
self.minsize(600, 500)
self.frame = customtkinter.CTkFrame(self, width=200, height=200, fg_color='black')
self.frame.place(x=10, y=10)
self.button = customtkinter.CTkButton(self, text="Smooth resize frame", command=self._resize_frame)
self.button.place(x=10, y=350)
self.label1 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='rounded value - 0')
self.label1.place(x=300, y=470, anchor='sw')
self.label2 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='direct value - 0.0')
self.label2.place(x=300, y=490, anchor='sw')
self.label3 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='fps - 0')
self.label3.place(x=300, y=450, anchor='sw')
def _resize_frame(self):
speed = 60
animator = Animator(200, 480, 1*speed, 60//speed, 'ease')
st = time.perf_counter()
for value in animator:
self.frame.configure(height=value, width=value)
self.update()
en = time.perf_counter()
self.label1.configure(text='rounded value - ' + str(round(value)))
self.label2.configure(text='direct value - ' + str(value))
self.label3.configure(text='fps - ' + str(1/(en-st)))
self.update()
st = time.perf_counter()
if __name__ == "__main__":
app = smooth_frame()
app.overrideredirect(False)
app.attributes('-topmost', True)
app.mainloop()
and i want to know how to set limit for frame size
can you explain what frame size?
I have encountered an error
i mean frame dimensions(width, height) to set set a limit when the button is pushed
and PyAnimator isn't in library so i can't use it i need your help
Give it a try - https://github.com/VasigaranAndAngel/PyAnimator
You can download PyAnimator from the link.
def _resize_frame(self):
animator = Animator(current_value=200, target_value=480, duration=1, fps=60, easing='ease')
st = time.perf_counter()
for value in animator:
self.frame.configure(height=value, width=value)
self.update()
en = time.perf_counter()
self.label1.configure(text='rounded value - ' + str(round(value)))
self.label2.configure(text='direct value - ' + str(value))
self.label3.configure(text='fps - ' + str(1/(en-st)))
self.update()
st = time.perf_counter()
You can see the line animator = Animator(current_value=200, target_value=480, duration=1, fps=60, easing='ease')
, which sets the current_value
and target_value
parameters. For current_value
, you should provide the current height and width of the frame as the starting point of the animation, and for target_value
, you should provide the desired height and width as the ending point of the animation. Currently, PyAnimator only supports animating one value at a time. Therefore, you will need to use two animators to animate both the height and width of the frame:
height_anim = Animator(current_value=200, target_value=480, duration=1, fps=60, easing='ease')
width_anim = Animator(current_value=200, target_value=680, duration=1, fps=60, easing='ease')
for height, width in zip(height_anim, width_anim):
self.frame.configure(height=height, width=width)
self.update()
This code will animate the height and width of the frame from 200 to 480 and from 200 to 680, respectively.
In the current implementation of the Animator
class, the time.sleep
function is used to introduce a delay between each frame of the animation. However, this approach is not very accurate and leads to timing issues, especially when the duration of the animation is long or the frame rate is high. To overcome this issue, I'm working on finding a better solution for timing the animation frames.
I hope this helps you understand better 🙂.
import customtkinter
from PyAnimator import Animator
import time
class smooth_frame(customtkinter.CTk):
def __init__(self):
super().__init__()
self.title("smooth frame")
self.maxsize(600, 500)
self.minsize(600, 500)
self.label1 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='rounded value - 0')
self.label1.place(x=300, y=470, anchor='sw')
self.label2 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='direct value - 0.0')
self.label2.place(x=300, y=490, anchor='sw')
self.label3 = customtkinter.CTkLabel(self, font=('Consolas', 15), text='fps - 0')
self.label3.place(x=300, y=450, anchor='sw')
self.frame = customtkinter.CTkFrame(self, width=100, height=100, fg_color='black')
self.frame.place(x=10, y=10)
self.frame.bind('<Enter>', self._resize_frame)
self.frame.bind('<Leave>', self._reduce_frame)
def _resize_frame(self, event):
speed = 60
animator = Animator(200, 480, 1*speed, 60//speed, 'ease')
st = time.perf_counter()
self.height_anim = Animator(current_value=100, target_value=220, duration=0.5, fps=60, easing='ease')
self.width_anim = Animator(current_value=100, target_value=220, duration=0.5, fps=60, easing='ease')
for height, width in zip(self.height_anim, self.width_anim):
self.frame.configure(height=height, width=width)
self.update()
for value in animator:
self.update()
en = time.perf_counter()
self.label1.configure(text='rounded value - ' + str(round(value)))
self.label2.configure(text='direct value - ' + str(value))
self.label3.configure(text='fps - ' + str(1/(en-st)))
self.update()
st = time.perf_counter()
def _reduce_frame(self, event):
speed = 60
animator = Animator(200, 480, 1*speed, 60//speed, 'ease')
st = time.perf_counter()
height_anim = Animator(current_value=self.height_anim.target_value, target_value=100, duration=0.5, fps=60, easing='ease')
width_anim = Animator(current_value=self.width_anim.target_value, target_value=100, duration=0.5, fps=60, easing='ease')
for height, width in zip(height_anim, width_anim):
self.frame.configure(height=height, width=width)
self.update()
for value in animator:
self.update()
en = time.perf_counter()
self.label1.configure(text='rounded value - ' + str(round(value)))
self.label2.configure(text='direct value - ' + str(value))
self.label3.configure(text='fps - ' + str(1/(en-st)))
self.update()
st = time.perf_counter()
if __name__ == "__main__":
app = smooth_frame()
app.overrideredirect(False)
app.attributes('-topmost', True)
app.mainloop()
try this, thank you
But we can't use this code in a complex graphic interface because is not optimised
The problem with the current implementation of Animator
is that it uses time.sleep()
for timing, which relies on the system clock. In Linux, it is possible to sleep for even 1 millisecond, but in Windows, the minimum time we can sleep is like 50 milliseconds (I don't know exactly). Moreover, the system clock is not entirely accurate and can change over time, causing issues with the timing of the animation. Although Python can handle about 7 million if statements with a small calculation per second on an average laptop, Python can sleep for 0.0002 milliseconds using a new method if that's properly designed. I'm experimenting with some methods to achieve better timing, but it's using a lot of resources and causing the program to lag. So, I'm still trying to figure out a solution 🥴.
If we increase the fps to 120 instead of 60, the animation will run at around 60 fps, which can make it a little smoother. However, when using Tkinter, all the elements are drawn in integer values. So if the animation runs slower and uses small decimal changes in value, those changes may not be visible. If you're okay with these limitations, I can help you in making animations.
This is one of my projects that involves using the Animator in multiple widgets.
These switches are animated at 60fps.
share the code
the time() library cannot simply create a timer when the program is starting
otherwise we will say that Python cannot allow the development of very elaborate graphical interfaces.
We can't say like that. Tkinter has some limitations and may not be the best choice for creating highly elaborate graphical interfaces. However, this does not mean that Python cannot be used for animations and graphics. It depends on the complexity and requirements of your project. If you need to create a very elaborate graphical interface, you may want to explore other frameworks that offer more flexibility and advanced features.
This function moves the switch and it runs when the switch is clicked or initialized. Still I'm working on it. I'm using threading in my program. But tkinter is not thread safe, so there might be some issues that arise because of that. However, since I'm just using the program for personal use, I'm not too concerned about it.
def _move_to(self, to: str):
current = self.switch_state
if to == LEFT: end = 0
elif to == RIGHT: end = self.WIDTH//2
elif to == CENTER: end = self.WIDTH//4
def do():
self.animating = True
# int(self._canvas.coords(self.button_front)[0])
animator = Animator(int(self.current_pos), end, duration=0.3*1, fps=60/1, easing=(0,.52,.39,1))
def kill(): self.animating = False; self.kill_anim = False; quit()
for value in animator:
self._canvas.moveto(self.button_front, value, '')
self.current_pos = value
self.update()
if self.kill_anim:
kill()
kill()
if self.animating:
self.kill_anim = True
# self.anim_thread.join()
self.anim_thread = threading.Thread(target=do)
self.anim_thread.start()
Threading is fine as long you go about it in the right way. Move any Tkinter operations to the main thread and use an event_generate()Â in your thread to let tkinter know when something has changed by using bind() in the main Tkinter thread.
Threading is fine as long you go about it in the right way. Move any Tkinter operations to the main thread and use an event_generate()Â in your thread to let tkinter know when something has changed by using bind() in the main Tkinter thread.
Oh cool, I wasn't aware of that. So threading is really not a problem then. Thanks for letting me know!
The idea is to integrate slow animations for moving or changing the dimension of a widget.