S1005403_RisCC/target_simulator/gui/simulation_controls.py

119 lines
4.9 KiB
Python

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("<<ComboboxSelected>>", 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("<ButtonPress-1>", lambda e: setattr(main_view, "_slider_is_dragging", True))
self.sim_slider.bind(
"<ButtonRelease-1>",
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