From ff7000796af21fab66b9d73c4da2b8fc6e723826 Mon Sep 17 00:00:00 2001 From: VALLONGOL Date: Wed, 5 Nov 2025 09:29:27 +0100 Subject: [PATCH] aggiunte info ownship su tab simulazione --- target_simulator/gui/main_view.py | 6 +- target_simulator/gui/simulation_controls.py | 155 +++++++++++++------- 2 files changed, 104 insertions(+), 57 deletions(-) diff --git a/target_simulator/gui/main_view.py b/target_simulator/gui/main_view.py index 77ba255..e411bf2 100644 --- a/target_simulator/gui/main_view.py +++ b/target_simulator/gui/main_view.py @@ -622,11 +622,15 @@ class MainView(tk.Tk): if az_deg is not None: self.ppi_widget.update_antenna_azimuth(az_deg, timestamp=az_ts) - # Update ownship orientation for the PPI display + # Update ownship state for both PPI orientation and status display ownship_state = self.simulation_hub.get_ownship_state() if ownship_state: ownship_heading = ownship_state.get("heading_deg", 0.0) self.ppi_widget.update_ownship_state(ownship_heading) + self.simulation_controls.update_ownship_display(ownship_state) + else: + # Ensure display is cleared if no ownship data is present + self.simulation_controls.update_ownship_display({}) if sim_is_running_now: diff --git a/target_simulator/gui/simulation_controls.py b/target_simulator/gui/simulation_controls.py index ed18190..5c345bf 100644 --- a/target_simulator/gui/simulation_controls.py +++ b/target_simulator/gui/simulation_controls.py @@ -1,76 +1,71 @@ +# target_simulator/gui/simulation_controls.py + import tkinter as tk -from tkinter import ttk, messagebox -from typing import Optional +from tkinter import ttk +from typing import Dict, Any + +from target_simulator.core.models import FPS_TO_KNOTS 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`. + """ + Encapsulates the Live Simulation Engine controls and ownship status display. """ 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. + # --- Variables for UI Binding --- 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)) + + # Ownship display variables + self.lat_var = tk.StringVar(value="-") + self.lon_var = tk.StringVar(value="-") + self.alt_var = tk.StringVar(value="-") + self.hdg_var = tk.StringVar(value="-") + self.gnd_speed_var = tk.StringVar(value="-") + self.vert_speed_var = tk.StringVar(value="-") - # Layout grid setup + # --- Widget Layout --- 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 + controls_frame.grid_columnconfigure(3, weight=1) - # 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.Frame(controls_frame).grid(row=0, column=2, sticky="ew", padx=10) # Spacer 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)) + self.multiplier_combo = ttk.Label(controls_frame, text="1x") # Placeholder, could be a combo + self.multiplier_combo.grid(row=0, column=5, sticky="w") - ttk.Label(controls_frame, text="Update Time (s):").grid(row=0, column=6, sticky="e", padx=(10, 2)) + ttk.Label(controls_frame, text="Update (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.update_time_entry.grid(row=0, column=7, sticky="w") - 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_button = ttk.Button(controls_frame, text="Reset Sim", command=getattr(main_view, "_on_reset_simulation", lambda: None)) + self.reset_button.grid(row=0, column=8, sticky="e", padx=10) 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) + self.reset_radar_button.grid(row=0, column=9, sticky="e") # 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(row=1, column=0, 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, + progress_frame, orient=tk.HORIZONTAL, variable=self.sim_slider_var, from_=0.0, to=1.0 ) 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)) @@ -83,43 +78,91 @@ class SimulationControls(ttk.LabelFrame): ) labels_frame = ttk.Frame(progress_frame) - labels_frame.grid(row=0, column=1, sticky="e", padx=(4, 4)) + labels_frame.grid(row=0, column=1, sticky="e") 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_elapsed_label.pack(side=tk.LEFT) + ttk.Label(labels_frame, text="/").pack(side=tk.LEFT, padx=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) + self.sim_total_label.pack(side=tk.LEFT) + + # --- Ownship State Display --- + ownship_frame = ttk.LabelFrame(self, text="Ownship State", padding=10) + ownship_frame.grid(row=2, column=0, sticky="ew", padx=5, pady=8) + ownship_frame.grid_columnconfigure(1, weight=1) + ownship_frame.grid_columnconfigure(3, weight=1) + + ttk.Label(ownship_frame, text="Latitude:").grid(row=0, column=0, sticky="w") + ttk.Label(ownship_frame, textvariable=self.lat_var, anchor="w").grid(row=0, column=1, sticky="ew", padx=5) + + ttk.Label(ownship_frame, text="Longitude:").grid(row=1, column=0, sticky="w") + ttk.Label(ownship_frame, textvariable=self.lon_var, anchor="w").grid(row=1, column=1, sticky="ew", padx=5) + + ttk.Label(ownship_frame, text="Altitude:").grid(row=0, column=2, sticky="w", padx=(10, 0)) + ttk.Label(ownship_frame, textvariable=self.alt_var, anchor="w").grid(row=0, column=3, sticky="ew", padx=5) + + ttk.Label(ownship_frame, text="Heading:").grid(row=1, column=2, sticky="w", padx=(10, 0)) + ttk.Label(ownship_frame, textvariable=self.hdg_var, anchor="w").grid(row=1, column=3, sticky="ew", padx=5) + + ttk.Label(ownship_frame, text="Ground Speed:").grid(row=0, column=4, sticky="w", padx=(10, 0)) + ttk.Label(ownship_frame, textvariable=self.gnd_speed_var, anchor="w").grid(row=0, column=5, sticky="ew", padx=5) + + ttk.Label(ownship_frame, text="Vertical Speed:").grid(row=1, column=4, sticky="w", padx=(10, 0)) + ttk.Label(ownship_frame, textvariable=self.vert_speed_var, anchor="w").grid(row=1, column=5, sticky="ew", padx=5) + # --- 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(row=3, 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, + 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) + notice_label.pack(side=tk.LEFT, fill=tk.X, expand=True) + ttk.Button(self.notice_frame, text="Dismiss", command=self.hide_notice).pack(side=tk.RIGHT, padx=(6, 0)) - 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 update_ownship_display(self, state: Dict[str, Any]): + """Updates the labels in the Ownship State frame.""" + if not state: + self.lat_var.set("-") + self.lon_var.set("-") + self.alt_var.set("-") + self.hdg_var.set("-") + self.gnd_speed_var.set("-") + self.vert_speed_var.set("-") + return + + # Latitude / Longitude + lat = state.get('latitude', 0.0) + lon = state.get('longitude', 0.0) + self.lat_var.set(f"{abs(lat):.5f}° {'N' if lat >= 0 else 'S'}") + self.lon_var.set(f"{abs(lon):.5f}° {'E' if lon >= 0 else 'W'}") + + # Altitude + alt_ft = state.get('altitude_ft', 0.0) + self.alt_var.set(f"{alt_ft:.1f} ft") + + # Heading + hdg_deg = state.get('heading_deg', 0.0) + self.hdg_var.set(f"{hdg_deg:.2f}°") + + # Ground Speed + vx_fps, vy_fps = state.get('velocity_xy_fps', (0.0, 0.0)) + gnd_speed_kn = (vx_fps**2 + vy_fps**2)**0.5 * FPS_TO_KNOTS + self.gnd_speed_var.set(f"{gnd_speed_kn:.1f} kn") + + # Vertical Speed + vz_fps = state.get('velocity_z_fps', 0.0) # Assuming 'vz' is part of state + self.vert_speed_var.set(f"{vz_fps:+.1f} ft/s") 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()