Untitled

 avatar
unknown
plain_text
a month ago
10 kB
3
Indexable
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