From 9fac7dde1ed6a0e16a7ccb4cbd1b3f6808041dde Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Mon, 3 Nov 2025 13:56:04 +0100 Subject: [PATCH] refactoring main_view, estratto simulation_controls --- target_simulator/gui/main_view.py | 227 ++++++++++---------- target_simulator/gui/simulation_controls.py | 118 ++++++++++ 2 files changed, 233 insertions(+), 112 deletions(-) create mode 100644 target_simulator/gui/simulation_controls.py diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 65bcce4..a50e387 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -283,124 +283,127 @@ class MainView(tk.Tk): lambda event: self._on_load_scenario(self.sim_scenario_combobox.get()), ) - engine_frame = ttk.LabelFrame(simulation_tab, text="Live Simulation Engine") - engine_frame.pack(fill=tk.X, padx=5, pady=10, anchor="n") - - # Use grid within engine_frame for a tidy multi-row layout that - # doesn't force the window to expand horizontally and keeps the PPI - # area visible. Configure columns so the middle spacer expands. - for i in range(10): - engine_frame.grid_columnconfigure(i, weight=0) - # Give the spacer column (3) and the main left column (0) flexible weight - engine_frame.grid_columnconfigure(0, weight=0) - engine_frame.grid_columnconfigure(3, weight=1) - - self.start_button = ttk.Button( - engine_frame, text="Start Live", command=self._on_start_simulation - ) - self.start_button.grid(row=0, column=0, sticky="w", padx=5, pady=5) - - self.stop_button = ttk.Button( - engine_frame, - text="Stop Live", - command=self._on_stop_simulation, - state=tk.DISABLED, - ) - self.stop_button.grid(row=0, column=1, sticky="w", padx=5, pady=5) - - # The application exposes a dedicated "Analysis" tab with the full - # analysis UI. The redundant "Show Analysis" button has been removed - # from the Simulation controls (use the Analysis tab instead). - - # spacer to push the following controls to the right - spacer = ttk.Frame(engine_frame) - spacer.grid(row=0, column=3, sticky="ew") - - ttk.Label(engine_frame, text="Speed:").grid( - row=0, column=4, sticky="e", padx=(10, 2), pady=5 - ) - self.time_multiplier_var = tk.StringVar(value="1x") - self.multiplier_combo = ttk.Combobox( - engine_frame, - 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( - "<>", self._on_time_multiplier_changed - ) - - ttk.Label(engine_frame, text="Update Time (s):").grid( - row=0, column=6, sticky="e", padx=(10, 2), pady=5 - ) - self.update_time_entry = ttk.Entry( - engine_frame, 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( - engine_frame, text="Reset State", command=self._on_reset_simulation - ) - self.reset_button.grid(row=0, column=8, sticky="e", padx=5, pady=5) - - self.reset_radar_button = ttk.Button( - engine_frame, text="Reset Radar", command=self._reset_radar_state - ) - self.reset_radar_button.grid(row=0, column=9, sticky="e", padx=5, pady=5) - - # --- Simulation progress bar / slider --- - # Place the progress frame on its own row below the control buttons - progress_frame = ttk.Frame(engine_frame) - # Place the progress frame on a dedicated grid row below the controls - progress_frame.grid( - row=1, column=0, columnspan=10, sticky="ew", padx=5, pady=(6, 2) - ) - 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, - # let grid manage length via sticky and column weights - ) - # configure progress_frame grid so slider expands and labels stay compact - progress_frame.grid_columnconfigure(0, weight=1) - progress_frame.grid_columnconfigure(1, weight=0) - self.sim_slider.grid(row=0, column=0, sticky="ew", padx=(4, 8)) - # Bind press/release to support seeking + # Extracted simulation controls into SimulationControls component try: - self.sim_slider.bind( - "", lambda e: setattr(self, "_slider_is_dragging", True) - ) - self.sim_slider.bind( - "", - lambda e: ( - setattr(self, "_slider_is_dragging", False), - self._on_seek(), - ), - ) + from target_simulator.gui.simulation_controls import SimulationControls + + self.simulation_controls = SimulationControls(simulation_tab, self) + self.simulation_controls.pack(fill=tk.X, padx=5, pady=10, anchor="n") + + # Preserve attribute names used throughout MainView for backward compatibility + self.start_button = self.simulation_controls.start_button + self.stop_button = self.simulation_controls.stop_button + self.multiplier_combo = self.simulation_controls.multiplier_combo + self.time_multiplier_var = self.simulation_controls.time_multiplier_var + self.update_time_entry = self.simulation_controls.update_time_entry + self.update_time = self.simulation_controls.update_time + self.reset_button = self.simulation_controls.reset_button + self.reset_radar_button = self.simulation_controls.reset_radar_button + self.sim_slider = self.simulation_controls.sim_slider + self.sim_elapsed_label = self.simulation_controls.sim_elapsed_label + self.sim_total_label = self.simulation_controls.sim_total_label except Exception: - pass + # If the extracted component fails, fall back to original inline layout + engine_frame = ttk.LabelFrame(simulation_tab, text="Live Simulation Engine") + engine_frame.pack(fill=tk.X, padx=5, pady=10, anchor="n") - # Time labels showing elapsed and total separately at the end of the bar - labels_frame = ttk.Frame(progress_frame) - labels_frame.grid(row=0, column=1, sticky="e", padx=(4, 4)) + # Use grid within engine_frame for a tidy multi-row layout that + # doesn't force the window to expand horizontally and keeps the PPI + # area visible. Configure columns so the middle spacer expands. + for i in range(10): + engine_frame.grid_columnconfigure(i, weight=0) + # Give the spacer column (3) and the main left column (0) flexible weight + engine_frame.grid_columnconfigure(0, weight=0) + engine_frame.grid_columnconfigure(3, weight=1) - 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) + self.start_button = ttk.Button( + engine_frame, text="Start Live", command=self._on_start_simulation + ) + self.start_button.grid(row=0, column=0, sticky="w", padx=5, pady=5) - slash_label = ttk.Label(labels_frame, text="/") - slash_label.grid(row=0, column=1, padx=(2, 2)) + self.stop_button = ttk.Button( + engine_frame, + text="Stop Live", + command=self._on_stop_simulation, + state=tk.DISABLED, + ) + self.stop_button.grid(row=0, column=1, sticky="w", padx=5, pady=5) - 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) + spacer = ttk.Frame(engine_frame) + spacer.grid(row=0, column=3, sticky="ew") + + ttk.Label(engine_frame, text="Speed:").grid( + row=0, column=4, sticky="e", padx=(10, 2), pady=5 + ) + self.time_multiplier_var = tk.StringVar(value="1x") + self.multiplier_combo = ttk.Combobox( + engine_frame, + 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( + "<>", self._on_time_multiplier_changed + ) + + ttk.Label(engine_frame, text="Update Time (s):").grid( + row=0, column=6, sticky="e", padx=(10, 2), pady=5 + ) + self.update_time_entry = ttk.Entry( + engine_frame, 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( + engine_frame, text="Reset State", command=self._on_reset_simulation + ) + self.reset_button.grid(row=0, column=8, sticky="e", padx=5, pady=5) + + self.reset_radar_button = ttk.Button( + engine_frame, text="Reset Radar", command=self._reset_radar_state + ) + self.reset_radar_button.grid(row=0, column=9, sticky="e", padx=5, pady=5) + + progress_frame = ttk.Frame(engine_frame) + progress_frame.grid(row=1, column=0, columnspan=10, sticky="ew", padx=5, pady=(6, 2)) + 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, + ) + progress_frame.grid_columnconfigure(0, weight=1) + progress_frame.grid_columnconfigure(1, weight=0) + self.sim_slider.grid(row=0, column=0, sticky="ew", padx=(4, 8)) + try: + self.sim_slider.bind( + "", lambda e: setattr(self, "_slider_is_dragging", True) + ) + self.sim_slider.bind( + "", + lambda e: ( + setattr(self, "_slider_is_dragging", False), + self._on_seek(), + ), + ) + 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) # --- TAB 3: LRU SIMULATION --- lru_tab = ttk.Frame(left_notebook) diff --git a/target_simulator/gui/simulation_controls.py b/target_simulator/gui/simulation_controls.py new file mode 100644 index 0000000..a2f398f --- /dev/null +++ b/target_simulator/gui/simulation_controls.py @@ -0,0 +1,118 @@ +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