Untitled
unknown
plain_text
10 months ago
11 kB
6
Indexable
import customtkinter as ctk
from PIL import Image, ImageTk, ImageDraw
import time
import random
import json
class CircularProgress(ctk.CTkCanvas):
_instances = []
def __init__(self, parent, bin_id, size=200, base_color='#f5f5f7'):
super().__init__(
parent,
width=size,
height=size,
bg='white',
highlightthickness=0
)
# Keep track of destroyed status
self._destroyed = False
CircularProgress._instances.append(self)
# Initialize properties
self.size = size
self.stroke_width = 8
self.radius = (size - self.stroke_width) / 2
self.center = size / 2
self.fill_level = 0
self.base_color = base_color
self.dark_mode = False
# Enhanced animation properties
self.last_press_time = 0
self.cooldown_period = 5.0 # Global cooldown between activations
self.can_press = True
self.is_pressed = False
self.press_scale = 1.0
self.press_animation_active = False
self.servo_active = False
# Animation timing constants
self.PRESS_SCALE_MIN = 0.9
self.PRESS_SCALE_MAX = 1.0
self.SCALE_STEP = 0.05
self.SERVO_OPEN_TIME = 10
# Pulse animation properties
self.pulse_active = False
self.pulse_scale = 1.0
self.pulse_growing = True
self.PULSE_MIN = 0.9
self.PULSE_MAX = 1.0
self.PULSE_STEP = 0.007
# Create image for drawing
self.im = Image.new('RGBA', (1000, 1000))
self.arc = None
# Bind events
self.bind('<Button-1>', self.on_press)
self.bind('<ButtonRelease-1>', self.on_release)
self.bind('<Destroy>', self._on_destroy)
# Initialize
self.target_fill_level = self.fill_level
self.animate()
# Set initial fill level
self.set_fill_level(random.randint(0, 100))
self.draw()
def _on_destroy(self, event):
if event.widget is self:
self._destroyed = True
try:
CircularProgress._instances.remove(self)
except ValueError:
pass
def get_progress_color(self):
if self.dark_mode:
if self.fill_level >= 80:
return '#ff3b30'
if self.fill_level >= 60:
return '#ff9f0a'
if self.fill_level >= 40:
return '#ffd60a'
return '#30d158'
else:
if self.fill_level >= 80:
return '#ff453a'
if self.fill_level >= 60:
return '#ff9f0a'
if self.fill_level >= 40:
return '#ffd60a'
return '#34c759'
def get_ring_color(self):
return '#2c2c2e' if self.dark_mode else '#e5e5e7'
def set_dark_mode(self, is_dark):
self.dark_mode = is_dark
self.configure(bg='#1c1c1e' if is_dark else '#f5f5f7')
self.draw()
def set_fill_level(self, level):
self.target_fill_level = float(level)
self.fill_level = float(level)
self.draw()
def animate(self):
if self._destroyed:
return
current_time = time.time()
# Update can_press status
if not self.can_press and current_time - self.last_press_time >= self.cooldown_period:
self.can_press = True
# Handle fill level animation
if self.target_fill_level != self.fill_level:
diff = self.target_fill_level - self.fill_level
self.fill_level += diff * 0.2
if abs(diff) < 0.5:
self.fill_level = self.target_fill_level
# Handle press animation
if self.press_animation_active:
if self.is_pressed:
self.press_scale = max(self.PRESS_SCALE_MIN, self.press_scale - self.SCALE_STEP)
if self.press_scale == self.PRESS_SCALE_MIN:
self.press_animation_active = self.is_pressed
else:
self.press_scale = min(self.PRESS_SCALE_MAX, self.press_scale + self.SCALE_STEP)
if self.press_scale == self.PRESS_SCALE_MAX:
self.press_animation_active = False
# Handle pulse animation during servo active state
if self.servo_active:
if self.pulse_growing:
self.pulse_scale = min(self.PULSE_MAX, self.pulse_scale + self.PULSE_STEP)
if self.pulse_scale >= self.PULSE_MAX:
self.pulse_growing = False
else:
self.pulse_scale = max(self.PULSE_MIN, self.pulse_scale - self.PULSE_STEP)
if self.pulse_scale <= self.PULSE_MIN:
self.pulse_growing = True
else:
# Reset scale when servo is not active
if self.pulse_scale != 1.0:
self.pulse_scale = min(1.0, self.pulse_scale + self.PULSE_STEP)
# Apply combined scale
final_scale = self.press_scale * self.pulse_scale
# Only redraw if there's been a change
if self.press_animation_active or self.servo_active or self.target_fill_level != self.fill_level:
self.draw(scale=final_scale)
self.after(16, self.animate)
def draw(self, scale=1.0):
self.delete('all')
# Define colors
circle_color = '#34c759' if self.dark_mode else '#30d158'
ring_color = self.get_ring_color()
progress_color = self.get_progress_color()
bg_color = '#1c1c1e' if self.dark_mode else '#f5f5f7'
# Apply scale transform for animation
scaled_size = self.size * scale
# Create new image for drawing
self.im = Image.new('RGBA', (1000, 1000), bg_color)
draw = ImageDraw.Draw(self.im)
# Calculate dimensions
outer_padding = 40
ring_width = 40
circle_padding = 15
# For light mode, draw white background circle
if not self.dark_mode:
draw.ellipse((outer_padding-ring_width, outer_padding-ring_width,
1000-outer_padding+ring_width, 1000-outer_padding+ring_width),
fill='white')
# Draw ring
draw.arc((outer_padding-ring_width, outer_padding-ring_width,
1000-outer_padding+ring_width, 1000-outer_padding+ring_width),
-90, 270, ring_color, ring_width)
# Draw progress arc
if self.fill_level > 0:
angle = int(self.fill_level * 360 / 100)
draw.arc((outer_padding-ring_width, outer_padding-ring_width,
1000-outer_padding+ring_width, 1000-outer_padding+ring_width),
-90, -90 + angle, progress_color, ring_width)
# Draw base circle
if not self.dark_mode:
draw.ellipse((outer_padding + circle_padding - 1,
outer_padding + circle_padding - 1,
1000-outer_padding - circle_padding + 1,
1000-outer_padding - circle_padding + 1),
fill='white')
draw.ellipse((outer_padding + circle_padding,
outer_padding + circle_padding,
1000-outer_padding - circle_padding,
1000-outer_padding - circle_padding),
fill=circle_color)
# Resize and create PhotoImage
resized = self.im.resize((int(scaled_size), int(scaled_size)), Image.Resampling.LANCZOS)
self.arc = ImageTk.PhotoImage(resized)
# Display the image
self.create_image(self.size/2, self.size/2, image=self.arc)
# Add text
self.create_text(
self.size/2,
self.size/2,
text=f"Button {self.bin_id}",
font=('Dongle', int(18 * self.press_scale), 'normal'),
fill='white' if self.dark_mode else 'black',
justify='center',
width=self.size-20
)
def on_press(self, event):
current_time = time.time()
if current_time - self.last_press_time >= self.cooldown_period:
print(f"Button {self.bin_id} pressed")
self.is_pressed = True
self.press_animation_active = True
self.last_press_time = current_time
self.can_press = False
# Toggle servo active state for demo
self.servo_active = not self.servo_active
print(f"Servo active: {self.servo_active}")
def on_release(self, event):
self.is_pressed = False
def main():
root = ctk.CTk()
root.title("Button Test")
root.geometry("1024x600")
# Set dark mode
DARK_MODE = False
ctk.set_appearance_mode("dark" if DARK_MODE else "light")
# Create main frame
main_frame = ctk.CTkFrame(root)
main_frame.pack(fill='both', expand=True)
# Create container for circles
circles_frame = ctk.CTkFrame(main_frame)
circles_frame.place(relx=0.5, rely=0.5, anchor='center')
# Create padding frame
padding_frame = ctk.CTkFrame(circles_frame)
padding_frame.pack(padx=50)
# Create 4 buttons
for i in range(4):
container = ctk.CTkFrame(padding_frame)
container.pack(side='left', padx=15)
progress = CircularProgress(
container,
bin_id=i+1,
size=220
)
progress.pack()
progress.set_dark_mode(DARK_MODE)
# Add dark mode toggle
dark_mode_switch = ctk.CTkSwitch(
main_frame,
text="Dark Mode",
command=lambda: toggle_dark_mode(DARK_MODE),
font=("Dongle", 16)
)
dark_mode_switch.place(relx=0.9, rely=0.1, anchor='center')
def toggle_dark_mode(current_mode):
nonlocal DARK_MODE
DARK_MODE = not current_mode
ctk.set_appearance_mode("dark" if DARK_MODE else "light")
for instance in CircularProgress._instances:
instance.set_dark_mode(DARK_MODE)
root.mainloop()
if __name__ == "__main__":
main()Editor is loading...
Leave a Comment