Untitled
unknown
plain_text
a year ago
10 kB
7
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()Editor is loading...
Leave a Comment