Untitled
class ManualRotationScreen(ctk.CTkToplevel): def __init__(self, parent, dark_mode: bool, language='EN', settings_menu=None): super().__init__(parent) self.language = getattr(parent, 'LANGUAGE', 'EN') self.settings_menu = settings_menu self.parent = parent self._destroyed = False self._closing = False # Store references to prevent garbage collection self._stored_widgets = [] # Use CircularProgress's class-level turntable reference self.turntable = CircularProgress._turntable self.bin_config = BinConfiguration() # Make it fullscreen and disable all close operations self.overrideredirect(True) self.geometry(f"{self.winfo_screenwidth()}x{self.winfo_screenheight()}+0+0") self.protocol("WM_DELETE_WINDOW", lambda: None) self.attributes('-topmost', True) # Create full-screen transparent event blocker self.event_blocker = ctk.CTkFrame( self, fg_color="transparent", corner_radius=0 ) self.event_blocker.place(relx=0, rely=0, relwidth=1, relheight=1) self._stored_widgets.append(self.event_blocker) # Set colors to match main screen self.dark_mode = dark_mode self.bg_color = '#1c1c1e' if dark_mode else '#f5f5f7' self.configure(fg_color=self.bg_color) # Create content frame on top of event blocker self.content_frame = ctk.CTkFrame( self.event_blocker, fg_color=self.bg_color, corner_radius=0 ) self.content_frame.place(relx=0, rely=0, relwidth=1, relheight=1) self._stored_widgets.append(self.content_frame) # Create title label self.title_label = ctk.CTkLabel( self.content_frame, text=TRANSLATIONS[self.language].get('manual_rotation', 'Manual Rotation'), font=("Dongle", 40, "bold"), text_color='white' if dark_mode else 'black' ) self.title_label.place(relx=0.5, rely=0.18, anchor='center') self._stored_widgets.append(self.title_label) # Create circles frame with transparent background self.circles_frame = ctk.CTkFrame(self.content_frame, fg_color="transparent") self.circles_frame.place(relx=0.5, rely=0.5, anchor='center') self._stored_widgets.append(self.circles_frame) # Add padding frame with transparent background self.padding_frame = ctk.CTkFrame(self.circles_frame, fg_color="transparent") self.padding_frame.pack(padx=50) self._stored_widgets.append(self.padding_frame) # Create list to track circle widgets self.circle_widgets = [] # Create Grey Circles for each bin for bin_config in self.bin_config.bins: container = ctk.CTkFrame(self.padding_frame, fg_color="transparent") container.pack(side='left', padx=15) self._stored_widgets.append(container) progress = RotationCircleButton( container, bin_id=bin_config['id'], size=220, language=language, dark_mode=dark_mode, command=lambda bid=bin_config['id']: self._rotate_to_position(bid) ) progress.pack() progress.set_dark_mode(dark_mode) self.circle_widgets.append(progress) self._stored_widgets.append(progress) # Add close button self.close_button = ctk.CTkLabel( self.content_frame, text="×", width=40, height=40, text_color='white' if dark_mode else 'black', font=("Arial", 32, "bold"), cursor="hand2" ) self.close_button.place(relx=0.995, rely=0.01, anchor="ne") self.close_button.bind("<Button-1>", self.handle_close) self.close_button.bind("<Enter>", lambda e: self.close_button.configure(text_color="#ff3b30")) self.close_button.bind("<Leave>", lambda e: self.close_button.configure( text_color='white' if dark_mode else 'black' )) self._stored_widgets.append(self.close_button) # Bind click events to the event blocker instead of the window self.event_blocker.bind('<Button-1>', self.check_click_location) # Bind destruction event self.bind("<Destroy>", self.on_destroy) def handle_close(self, event=None): """Handle closing the screen safely""" if self._closing or self._destroyed: return self._closing = True try: # Disable all circle widgets first for circle in self.circle_widgets: circle.configure(state="disabled") # Close settings menu if open if self.settings_menu and self.settings_menu.is_open: self.settings_menu.close_menu() # Apply temporary cooldown to all instances for instance in CircularProgress._instances: if not instance._destroyed: instance.can_press = False instance.is_pressed = False instance.press_animation_active = False # Schedule re-enabling after delay self.after(1500, self._re_enable_instances) # Schedule actual destruction self.after(100, self.destroy) except Exception as e: print(f"Error during close: {e}") self.destroy() def _re_enable_instances(self): """Re-enable CircularProgress instances""" try: for instance in CircularProgress._instances: if not instance._destroyed: instance.can_press = True except Exception as e: print(f"Error re-enabling instances: {e}") def check_click_location(self, event): """Safely check click location and handle closing""" if self._closing or self._destroyed: return # Get the click location relative to the window click_x = event.x_root - self.winfo_rootx() click_y = event.y_root - self.winfo_rooty() # Check if click is inside any circle for circle in self.circle_widgets: circle_x = circle.winfo_x() + circle.winfo_width() / 2 circle_y = circle.winfo_y() + circle.winfo_height() / 2 radius = circle.size / 2 distance = ((click_x - circle_x) ** 2 + (click_y - circle_y) ** 2) ** 0.5 if distance <= radius: return # Click was inside a circle # If we get here, click was outside circles self.handle_close() def on_destroy(self, event): """Handle destruction of the window""" if event.widget is self and not self._destroyed: self._destroyed = True # Clear stored widget references self._stored_widgets.clear() # Ensure cleanup happens self._re_enable_instances() def _rotate_to_position(self, bin_id): """Rotate to the specified bin position""" if self._closing or self._destroyed: return print(f"Starting rotation to {bin_id}") if not self.turntable or not self.turntable.is_initialized: print("No turntable controller available or not initialized") MessageDialog( self.winfo_toplevel(), TRANSLATIONS[self.language].get('please_wait', 'Please wait'), self.dark_mode ) return # Create loading screen loading_screen = LoadingScreen( self.winfo_toplevel(), message=TRANSLATIONS[self.language].get('moving_bin', 'Moving to position...'), dark_mode=self.dark_mode, language=self.language ) def perform_rotation(): try: if self._destroyed: return # Calculate target position target_position = self.turntable.positions[bin_id] # Calculate and execute movement steps, direction = self.turntable._calculate_steps(target_position) self.turntable._move_motor(steps, direction) self.turntable.current_position = target_position # Close loading screen after movement completes if not self._destroyed: self.after(500, loading_screen.close) except Exception as e: print(f"Error during rotation: {e}") if not self._destroyed: self.after(0, loading_screen.close) # Run rotation in separate thread thread = threading.Thread(target=perform_rotation, daemon=True) thread.start()
Leave a Comment