Untitled
import customtkinter as ctk from PIL import Image, ImageTk, ImageDraw import json import time import threading 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 ) self._destroyed = False CircularProgress._instances.append(self) # Store bin ID and configuration self.bin_id = bin_id self.bin_config = self.load_bin_config(bin_id) # 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 # Animation properties self.last_press_time = 0 self.cooldown_period = 5.0 self.can_press = True self.is_pressed = False self.press_scale = 1.0 self.press_animation_active = False # 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() self.load_fill_level() self.draw() def load_bin_config(self, bin_id): try: with open('bin_config.json', 'r') as f: config = json.load(f) return next((bin_config for bin_config in config['bins'] if bin_config['id'] == bin_id), None) except Exception as e: print(f"Error loading bin config: {e}") # Return default configuration return { 'id': bin_id, 'name': f'Bin {bin_id[-1]}', 'color': '#e5e5e7', 'color_dark': '#2c2c2e', 'is_default': True } def load_fill_level(self): try: with open('fill_levels.json', 'r') as f: levels = json.load(f) self.set_fill_level(levels.get(self.bin_id, 0)) except Exception as e: print(f"Error loading fill level: {e}") self.set_fill_level(0) def set_fill_level(self, level): self.target_fill_level = float(level) self.fill_level = float(level) self.draw() self.check_fill_level() def check_fill_level(self): if self.fill_level >= 90: current_time = time.time() if not hasattr(self, '_last_alert_time') or \ (current_time - self._last_alert_time >= 1.0): self._last_alert_time = current_time print(f"Bin {self.bin_id} is nearly full!") # Add your alert handling here 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 _on_destroy(self, event): if event.widget is self: self._destroyed = True try: CircularProgress._instances.remove(self) except ValueError: pass 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(0.95, self.press_scale - 0.05) if self.press_scale == 0.95: self.press_animation_active = self.is_pressed else: self.press_scale = min(1.0, self.press_scale + 0.05) if self.press_scale == 1.0: self.press_animation_active = False self.draw() self.after(16, self.animate) def draw(self): self.delete('all') # Get colors from bin configuration circle_color = self.bin_config['color_dark'] if self.dark_mode else self.bin_config['color'] 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 press animation scaled_size = self.size * self.press_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 # Draw white background circle in light mode 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 (trough) 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) # Calculate position x = (self.size - scaled_size) / 2 y = (self.size - scaled_size) / 2 # Display the image self.create_image(self.size/2, self.size/2, image=self.arc) # Add text text_color = self.get_text_color(circle_color) self.create_text( self.size/2, self.size/2, text=self.bin_config['name'], font=('Dongle', int(18 * self.press_scale), 'normal'), fill=text_color, justify='center', width=self.size-20 ) def get_text_color(self, background_color): color = background_color.lstrip('#') rgb = tuple(int(color[i:i+2], 16) for i in (0, 2, 4)) luminance = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255 return '#000000' if luminance > 0.5 else '#ffffff' def on_press(self, event): current_time = time.time() if current_time - self.last_press_time >= self.cooldown_period: self.is_pressed = True self.press_animation_active = True self.last_press_time = current_time self.can_press = False print(f"{self.bin_id} button pressed") # Add your button press handling here def on_release(self, event): self.is_pressed = False def create_main_interface(): root = ctk.CTk() root.title("Bin Interface") root.geometry("1024x600+0+0") root.overrideredirect(True) # Set dark mode (can be changed) dark_mode = False bg_color = '#1c1c1e' if dark_mode else '#f5f5f7' main_frame = ctk.CTkFrame(root, fg_color=bg_color) main_frame.pack(fill='both', expand=True) circles_frame = ctk.CTkFrame(main_frame, fg_color=bg_color) circles_frame.place(relx=0.5, rely=0.5, anchor='center') padding_frame = ctk.CTkFrame(circles_frame, fg_color=bg_color) padding_frame.pack(padx=50) # Create four bins for i in range(1, 5): container = ctk.CTkFrame(padding_frame, fg_color=bg_color) container.pack(side='left', padx=15) progress = CircularProgress( container, bin_id=f'bin{i}', size=220 ) progress.pack() progress.set_dark_mode(dark_mode) return root if __name__ == "__main__": root = create_main_interface() root.mainloop()
Leave a Comment