import tkinter as tk from tkinter import ttk from typing import Optional class SimulationControls(ttk.LabelFrame): """Encapsulates the Live Simulation Engine controls. This keeps the UI widgets (Start/Stop, Speed, Update Time, Reset, slider) in a focused component. It uses the provided main_view reference for callbacks and shared Tk variables to preserve existing behavior while keeping the layout code out of `main_view.py`. """ def __init__(self, parent, main_view): super().__init__(parent, text="Live Simulation Engine") self.main_view = main_view # Use main_view's variables so external code referencing them keeps # working unchanged. self.time_multiplier_var = getattr(main_view, "time_multiplier_var", tk.StringVar(value="1x")) self.update_time = getattr(main_view, "update_time", tk.DoubleVar(value=1.0)) self.sim_slider_var = getattr(main_view, "sim_slider_var", tk.DoubleVar(value=0.0)) # Layout grid setup (mirror MainView original layout) for i in range(10): self.grid_columnconfigure(i, weight=0) self.grid_columnconfigure(0, weight=0) self.grid_columnconfigure(3, weight=1) # Buttons self.start_button = ttk.Button(self, text="Start Live", command=self._start) self.start_button.grid(row=0, column=0, sticky="w", padx=5, pady=5) self.stop_button = ttk.Button(self, text="Stop Live", command=self._stop, state=tk.DISABLED) self.stop_button.grid(row=0, column=1, sticky="w", padx=5, pady=5) # spacer spacer = ttk.Frame(self) spacer.grid(row=0, column=3, sticky="ew") ttk.Label(self, text="Speed:").grid(row=0, column=4, sticky="e", padx=(10, 2), pady=5) self.multiplier_combo = ttk.Combobox( self, textvariable=self.time_multiplier_var, values=["1x", "2x", "4x", "10x", "20x"], state="readonly", width=4, ) self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5), pady=5) self.multiplier_combo.bind("<>", getattr(main_view, "_on_time_multiplier_changed", lambda e=None: None)) ttk.Label(self, text="Update Time (s):").grid(row=0, column=6, sticky="e", padx=(10, 2), pady=5) self.update_time_entry = ttk.Entry(self, textvariable=self.update_time, width=5) self.update_time_entry.grid(row=0, column=7, sticky="w", padx=(0, 5), pady=5) self.reset_button = ttk.Button(self, text="Reset State", command=getattr(main_view, "_on_reset_simulation", lambda: None)) self.reset_button.grid(row=0, column=8, sticky="e", padx=5, pady=5) self.reset_radar_button = ttk.Button(self, text="Reset Radar", command=getattr(main_view, "_reset_radar_state", lambda: None)) self.reset_radar_button.grid(row=0, column=9, sticky="e", padx=5, pady=5) # Progress slider row progress_frame = ttk.Frame(self) progress_frame.grid(row=1, column=0, columnspan=10, sticky="ew", padx=5, pady=(6, 2)) progress_frame.grid_columnconfigure(0, weight=1) progress_frame.grid_columnconfigure(1, weight=0) self.sim_slider = ttk.Scale( progress_frame, orient=tk.HORIZONTAL, variable=self.sim_slider_var, from_=0.0, to=1.0, command=lambda v: None, ) self.sim_slider.grid(row=0, column=0, sticky="ew", padx=(4, 8)) # Bind press/release to support seeking (use main_view handler) try: self.sim_slider.bind("", lambda e: setattr(main_view, "_slider_is_dragging", True)) self.sim_slider.bind( "", lambda e: ( setattr(main_view, "_slider_is_dragging", False), getattr(main_view, "_on_seek", lambda: None)(), ), ) except Exception: pass labels_frame = ttk.Frame(progress_frame) labels_frame.grid(row=0, column=1, sticky="e", padx=(4, 4)) self.sim_elapsed_label = ttk.Label(labels_frame, text="0.0s", width=8, anchor=tk.E) self.sim_elapsed_label.grid(row=0, column=0) slash_label = ttk.Label(labels_frame, text="/") slash_label.grid(row=0, column=1, padx=(2, 2)) self.sim_total_label = ttk.Label(labels_frame, text="0.0s", width=8, anchor=tk.W) self.sim_total_label.grid(row=0, column=2) # Button handlers that delegate to main_view methods def _start(self): try: if hasattr(self.main_view, "_on_start_simulation"): self.main_view._on_start_simulation() except Exception: raise def _stop(self): try: if hasattr(self.main_view, "_on_stop_simulation"): self.main_view._on_stop_simulation() except Exception: raise