import tkinter as tk from tkinter import ttk, messagebox 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 self.grid_columnconfigure(0, weight=1) # Main controls frame controls_frame = ttk.Frame(self) controls_frame.grid(row=0, column=0, sticky="ew", padx=5, pady=5) # Configure columns inside the controls frame for i in range(10): controls_frame.grid_columnconfigure(i, weight=0) controls_frame.grid_columnconfigure(3, weight=1) # Spacer # Buttons self.start_button = ttk.Button(controls_frame, text="Start Live", command=self._start) self.start_button.grid(row=0, column=0, sticky="w") self.stop_button = ttk.Button(controls_frame, text="Stop Live", command=self._stop, state=tk.DISABLED) self.stop_button.grid(row=0, column=1, sticky="w", padx=5) ttk.Frame(controls_frame).grid(row=0, column=3, sticky="ew") ttk.Label(controls_frame, text="Speed:").grid(row=0, column=4, sticky="e", padx=(10, 2)) self.multiplier_combo = ttk.Label(controls_frame, text="1x") self.multiplier_combo.grid(row=0, column=5, sticky="w", padx=(0, 5)) ttk.Label(controls_frame, text="Update Time (s):").grid(row=0, column=6, sticky="e", padx=(10, 2)) self.update_time_entry = ttk.Entry(controls_frame, textvariable=self.update_time, width=5) self.update_time_entry.grid(row=0, column=7, sticky="w", padx=(0, 5)) self.reset_button = ttk.Button(controls_frame, text="Reset State", command=getattr(main_view, "_on_reset_simulation", lambda: None)) self.reset_button.grid(row=0, column=8, sticky="e", padx=5) self.reset_radar_button = ttk.Button(controls_frame, 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) # 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)) 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)(), ), ) 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) ttk.Label(labels_frame, text="/").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) # --- Non-modal notice area --- self.notice_var = tk.StringVar(value="") self.notice_frame = ttk.Frame(self) # This frame is placed at row=2, under the progress bar self.notice_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=(5, 0)) self.notice_frame.grid_remove() # Hidden by default self.notice_label = tk.Label( self.notice_frame, textvariable=self.notice_var, bg="#fff3cd", fg="#6a4b00", anchor="w", relief=tk.SOLID, bd=1, padx=6, pady=2, ) self.notice_label.pack(side=tk.LEFT, fill=tk.X, expand=True) self.dismiss_button = ttk.Button(self.notice_frame, text="Dismiss", command=self.hide_notice) self.dismiss_button.pack(side=tk.RIGHT, padx=(6, 0)) def show_notice(self, message: str): """Displays the notification bar with a message.""" self.notice_var.set(message) self.notice_frame.grid() def hide_notice(self): """Hides the notification bar.""" self.notice_var.set("") self.notice_frame.grid_remove() def _start(self): if hasattr(self.main_view, "_on_start_simulation"): self.main_view._on_start_simulation() def _stop(self): if hasattr(self.main_view, "_on_stop_simulation"): self.main_view._on_stop_simulation()