# --- START OF FILE ui.py --- # ui.py """ THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. Defines the user interface components for the Control Panel application, including the main control panel area, status bar, map parameters, info display with Google Maps/Earth links, and helper functions for window creation. The main ControlPanel frame is designed to be placed within a container managed by the main application. The metadata display components are now created and managed directly by the main application (ControlPanelApp). """ # Standard library imports import logging from typing import TYPE_CHECKING, Dict, Tuple, Optional # Third-party imports import tkinter as tk from tkinter import ttk # Removed ScrolledText import as it's no longer created here # from tkinter.scrolledtext import ScrolledText # Local application imports import config # Type hinting for App reference if TYPE_CHECKING: # Assuming ControlPanelApp is defined in ControlPanel.py from ControlPanel import ControlPanelApp class ControlPanel(ttk.Frame): # This is the main panel holding user controls """ Main control panel frame containing SAR, MFD, Map parameter widgets, information displays, statistics labels, and interaction buttons. This frame is typically placed in the main application window's container. """ def __init__(self, parent: tk.Widget, app: "ControlPanelApp", *args, **kwargs): """Initializes the ControlPanel frame.""" log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Initializing ControlPanel frame...") super().__init__(parent, *args, **kwargs) self.app: "ControlPanelApp" = app # --- StringVars for UI elements --- self.sar_center_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.sar_orientation_var = tk.StringVar(value="N/A") self.sar_size_km_var = tk.StringVar(value="N/A") self.mouse_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.map_mouse_coords_var = tk.StringVar(value="Lat=N/A, Lon=N/A") self.sar_lat_shift_var = tk.StringVar( value=f"{self.app.state.sar_lat_shift_deg:.6f}" ) self.sar_lon_shift_var = tk.StringVar( value=f"{self.app.state.sar_lon_shift_deg:.6f}" ) self.dropped_stats_var = tk.StringVar(value="Drop (Q): S=0, M=0, Tk=0, Mo=0") self.incomplete_stats_var = tk.StringVar(value="Incmpl (RX): S=0, M=0") # Checkbox variable for metadata toggle (still needed here) self.show_meta_var = tk.BooleanVar(value=self.app.state.display_sar_metadata) # --- References to UI widgets --- self.mfd_color_labels: Dict[str, tk.Label] = {} # References to metadata widgets are REMOVED from this class # --- Initialize UI structure --- self.init_ui() # Call the UI building method logging.debug(f"{log_prefix} ControlPanel frame initialization complete.") # --- UI Construction Method --- def init_ui(self): """Initializes and arranges the user interface widgets within this frame.""" log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Starting init_ui widget creation...") # This frame (self) is placed by its parent (container_frame in App) # DO NOT pack or grid self here. # --- 1. SAR Parameters Frame --- self.sar_params_frame = ttk.Labelframe(self, text="SAR Parameters", padding=5) self.sar_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=(5, 2)) sar_row = 0 # Row counter for SAR grid # Test Image Checkbox self.test_image_var = tk.IntVar(value=1 if config.ENABLE_TEST_MODE else 0) self.test_image_check = ttk.Checkbutton( self.sar_params_frame, text="Test Image", variable=self.test_image_var, command=self.app.update_image_mode, ) self.test_image_check.grid( row=sar_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2 ) # Record SAR Checkbox self.record_sar_var = tk.BooleanVar(value=config.DEFAULT_SAR_RECORDING_ENABLED) self.record_sar_check = ttk.Checkbutton( self.sar_params_frame, text="Record SAR", variable=self.record_sar_var, command=self.app.toggle_sar_recording, ) self.record_sar_check.grid( row=sar_row, column=2, columnspan=2, sticky=tk.W, padx=5, pady=2 ) sar_row += 1 # SAR Size Combobox self.sar_size_label = ttk.Label(self.sar_params_frame, text="Size:") self.sar_size_label.grid( row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.sar_size_combo = ttk.Combobox( self.sar_params_frame, values=config.SAR_SIZE_FACTORS, state="readonly", width=6, ) try: # Set initial value based on current state factor = 1 if self.app.state.sar_display_width > 0: factor = max(1, config.SAR_WIDTH // self.app.state.sar_display_width) sz_str = f"1:{factor}" if sz_str in config.SAR_SIZE_FACTORS: self.sar_size_combo.set(sz_str) else: self.sar_size_combo.set(config.DEFAULT_SAR_SIZE) except Exception: # Fallback to default if error self.sar_size_combo.set(config.DEFAULT_SAR_SIZE) self.sar_size_combo.grid( row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1 ) self.sar_size_combo.bind("<>", self.app.update_sar_size) # SAR Palette Combobox self.palette_label = ttk.Label(self.sar_params_frame, text="Palette:") self.palette_label.grid(row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1) self.palette_combo = ttk.Combobox( self.sar_params_frame, values=config.COLOR_PALETTES, state="readonly", width=8, ) self.palette_combo.set(self.app.state.sar_palette) # Set from state self.palette_combo.grid( row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1 ) self.palette_combo.bind("<>", self.app.update_sar_palette) sar_row += 1 # SAR Contrast Slider self.contrast_label = ttk.Label(self.sar_params_frame, text="Contrast:") self.contrast_label.grid( row=sar_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.contrast_scale = ttk.Scale( self.sar_params_frame, orient=tk.HORIZONTAL, from_=0.1, to=3.0, value=self.app.state.sar_contrast, # Set from state command=self.app.update_contrast, ) self.contrast_scale.grid( row=sar_row, column=1, sticky=tk.EW, padx=(0, 10), pady=1 ) # SAR Brightness Slider self.brightness_label = ttk.Label(self.sar_params_frame, text="Brightness:") self.brightness_label.grid( row=sar_row, column=2, sticky=tk.W, padx=(0, 2), pady=1 ) self.brightness_scale = ttk.Scale( self.sar_params_frame, orient=tk.HORIZONTAL, from_=-100, to=100, value=self.app.state.sar_brightness, # Set from state command=self.app.update_brightness, ) self.brightness_scale.grid( row=sar_row, column=3, sticky=tk.EW, padx=(0, 5), pady=1 ) # SAR Metadata Checkbox (Still created here as it belongs logically with SAR params) sar_row += 1 # self.show_meta_var is already created in __init__ self.show_meta_check = ttk.Checkbutton( self.sar_params_frame, text="Show SAR Metadata", variable=self.show_meta_var, command=self.app.toggle_sar_metadata_display, # Link to app callback ) self.show_meta_check.grid( row=sar_row, column=0, columnspan=4, sticky=tk.W, padx=5, pady=(5, 2) ) # Configure SAR frame column weights self.sar_params_frame.columnconfigure(1, weight=1) # Allow sliders to expand self.sar_params_frame.columnconfigure(3, weight=1) # --- 2. MFD Parameters Frame --- self.mfd_params_frame = ttk.Labelframe(self, text="MFD Parameters", padding=5) self.mfd_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) mfd_categories_ordered = [ "Occlusion", "Cat A", "Cat B", "Cat C", "Cat C1", "Cat C2", "Cat C3", ] num_categories = len(mfd_categories_ordered) for index, name in enumerate(mfd_categories_ordered): row = index // 2 col_offset = 0 if (index % 2 == 0) else 4 # Category Label cat_label = ttk.Label(self.mfd_params_frame, text=f"{name}:") cat_label.grid( row=row, column=0 + col_offset, sticky=tk.W, padx=(5, 1), pady=1 ) # Intensity Slider Variable and Widget intensity_var = tk.IntVar(value=config.DEFAULT_MFD_INTENSITY) try: intensity_var.set( self.app.state.mfd_params["categories"][name]["intensity"] ) except Exception: pass intensity_scale = ttk.Scale( self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=intensity_var, command=lambda v, n=name, var=intensity_var: ( self.app.update_mfd_category_intensity(n, var.get()) ), ) intensity_scale.grid( row=row, column=1 + col_offset, sticky=tk.EW, padx=1, pady=1 ) # Color Chooser Button color_button = ttk.Button( self.mfd_params_frame, text="Color", width=5, command=lambda n=name: self.app.choose_mfd_category_color(n), ) color_button.grid( row=row, column=2 + col_offset, sticky=tk.W, padx=1, pady=1 ) # Color Preview Label color_label = tk.Label( self.mfd_params_frame, text="", width=3, relief=tk.SUNKEN, borderwidth=1 ) try: bgr = self.app.state.mfd_params["categories"][name]["color"] hex_color = f"#{bgr[2]:02x}{bgr[1]:02x}{bgr[0]:02x}" color_label.config(background=hex_color) except Exception: color_label.config(background="grey") color_label.grid( row=row, column=3 + col_offset, sticky=tk.W, padx=(1, 5), pady=1 ) self.mfd_color_labels[name] = color_label # Raw Map Intensity Slider last_cat_row = (num_categories - 1) // 2 raw_map_col_offset = 4 if (num_categories % 2 != 0) else 0 raw_map_row = last_cat_row if (num_categories % 2 != 0) else last_cat_row + 1 raw_map_label = ttk.Label(self.mfd_params_frame, text="Raw Map:") raw_map_label.grid( row=raw_map_row, column=0 + raw_map_col_offset, sticky=tk.W, padx=(5, 1), pady=1, ) raw_map_intensity_var = tk.IntVar(value=config.DEFAULT_MFD_RAW_MAP_INTENSITY) try: raw_map_intensity_var.set(self.app.state.mfd_params["raw_map_intensity"]) except Exception: pass self.mfd_raw_map_intensity_var = raw_map_intensity_var # Keep reference raw_map_scale = ttk.Scale( self.mfd_params_frame, orient=tk.HORIZONTAL, length=100, from_=0, to=255, variable=raw_map_intensity_var, command=lambda v: self.app.update_mfd_raw_map_intensity( raw_map_intensity_var.get() ), ) raw_map_scale.grid( row=raw_map_row, column=1 + raw_map_col_offset, columnspan=3, sticky=tk.EW, padx=(1, 5), pady=1, ) # Configure MFD frame column weights self.mfd_params_frame.columnconfigure(1, weight=1) self.mfd_params_frame.columnconfigure(5, weight=1) # --- 3. Map Parameters Frame --- self.map_params_frame = ttk.Labelframe(self, text="Map Parameters", padding=5) self.map_params_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) map_row = 0 # Row counter for Map grid # Map Size Combobox self.map_size_label = ttk.Label(self.map_params_frame, text="Map Display Size:") self.map_size_label.grid( row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1 ) self.map_size_combo = ttk.Combobox( self.map_params_frame, values=config.MAP_SIZE_FACTORS, state="readonly", width=6, ) self.map_size_combo.set(config.DEFAULT_MAP_SIZE) self.map_size_combo.grid( row=map_row, column=1, sticky=tk.EW, padx=(2, 10), pady=1 ) self.map_size_combo.bind("<>", self.app.update_map_size) # Save Map Button self.save_map_button = ttk.Button( self.map_params_frame, text="Save Map View", command=self.app.save_current_map_view, ) self.save_map_button.grid( row=map_row, column=2, columnspan=4, sticky=tk.E, padx=5, pady=1 ) map_row += 1 # SAR Overlay Checkbox self.sar_overlay_var = tk.BooleanVar( value=self.app.state.map_sar_overlay_enabled ) self.sar_overlay_check = ttk.Checkbutton( self.map_params_frame, text="Show SAR Overlay on Map", variable=self.sar_overlay_var, command=self.app.toggle_sar_overlay, ) self.sar_overlay_check.grid( row=map_row, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2 ) map_row += 1 # SAR Overlay Alpha Slider self.alpha_label = ttk.Label(self.map_params_frame, text="SAR Overlay Alpha:") self.alpha_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) self.sar_overlay_alpha_var = tk.DoubleVar( value=self.app.state.map_sar_overlay_alpha ) self.alpha_scale = ttk.Scale( self.map_params_frame, orient=tk.HORIZONTAL, from_=0.0, to=1.0, variable=self.sar_overlay_alpha_var, ) self.alpha_scale.bind("", self.app.on_alpha_slider_release) self.alpha_scale.grid( row=map_row, column=1, columnspan=5, sticky=tk.EW, padx=(0, 5), pady=1 ) map_row += 1 # SAR Shift Inputs and Apply Button shift_label = ttk.Label(self.map_params_frame, text="SAR Shift (deg):") shift_label.grid(row=map_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) lat_label = ttk.Label(self.map_params_frame, text="Lat:") lat_label.grid(row=map_row, column=1, sticky=tk.W, padx=(0, 0), pady=1) self.lat_shift_entry = ttk.Entry( self.map_params_frame, textvariable=self.sar_lat_shift_var, width=10 ) self.lat_shift_entry.grid( row=map_row, column=2, sticky=tk.W, padx=(0, 5), pady=1 ) lon_label = ttk.Label(self.map_params_frame, text="Lon:") lon_label.grid(row=map_row, column=3, sticky=tk.W, padx=(5, 0), pady=1) self.lon_shift_entry = ttk.Entry( self.map_params_frame, textvariable=self.sar_lon_shift_var, width=10 ) self.lon_shift_entry.grid( row=map_row, column=4, sticky=tk.W, padx=(0, 5), pady=1 ) self.apply_shift_button = ttk.Button( self.map_params_frame, text="Apply Shift", command=self.app.apply_sar_overlay_shift, ) self.apply_shift_button.grid( row=map_row, column=5, sticky=tk.E, padx=(5, 5), pady=1 ) # Configure Map frame column weights self.map_params_frame.columnconfigure(2, weight=1) self.map_params_frame.columnconfigure(4, weight=1) # --- 4. Info Display Frame --- self.info_display_frame = ttk.Labelframe(self, text="Info Display", padding=5) self.info_display_frame.pack(side=tk.TOP, fill=tk.X, padx=5, pady=2) info_row = 0 # Row counter for Info grid button_width = 3 # Standard width for Go/GE buttons # --- Row 0: SAR Center Coords --- ref_label = ttk.Label(self.info_display_frame, text="SAR Center:") ref_label.grid(row=info_row, column=0, sticky=tk.W, padx=(5, 2), pady=1) self.sar_center_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_center_coords_var, state="readonly", width=35, ) self.sar_center_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) self.ref_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("sar_center"), ) self.ref_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.ref_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("sar_center"), ) self.ref_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) info_row += 1 # --- Row 1: Image Orient & Image Size --- orient_label = ttk.Label(self.info_display_frame, text="Image Orient:") orient_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.sar_orientation_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_orientation_var, state="readonly", width=15, ) self.sar_orientation_entry.grid( row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1 ) size_label = ttk.Label(self.info_display_frame, text="Image Size:") size_label.grid(row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1) self.sar_size_entry = ttk.Entry( self.info_display_frame, textvariable=self.sar_size_km_var, state="readonly", width=25, ) self.sar_size_entry.grid( row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1 ) info_row += 1 # --- Row 2: SAR Mouse Coords --- sar_mouse_label = ttk.Label(self.info_display_frame, text="SAR Mouse:") sar_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.mouse_latlon_entry = ttk.Entry( self.info_display_frame, textvariable=self.mouse_coords_var, state="readonly", width=35, ) self.mouse_latlon_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) self.sar_mouse_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("sar_mouse"), ) self.sar_mouse_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.sar_mouse_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("sar_mouse"), ) self.sar_mouse_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) info_row += 1 # --- Row 3: Map Mouse Coords --- map_mouse_label = ttk.Label(self.info_display_frame, text="Map Mouse:") map_mouse_label.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.map_mouse_latlon_entry = ttk.Entry( self.info_display_frame, textvariable=self.map_mouse_coords_var, state="readonly", width=35, ) self.map_mouse_latlon_entry.grid( row=info_row, column=1, columnspan=3, sticky=tk.EW, padx=(0, 2), pady=1 ) self.map_mouse_gmaps_button = ttk.Button( self.info_display_frame, text="Go", width=button_width, command=lambda: self.app.go_to_google_maps("map_mouse"), ) self.map_mouse_gmaps_button.grid( row=info_row, column=4, sticky=tk.E, padx=(0, 1), pady=1 ) self.map_mouse_gearth_button = ttk.Button( self.info_display_frame, text="GE", width=button_width, command=lambda: self.app.go_to_google_earth("map_mouse"), ) self.map_mouse_gearth_button.grid( row=info_row, column=5, sticky=tk.E, padx=(0, 5), pady=1 ) info_row += 1 # --- Row 4: Drop & Incomplete Stats --- dropped_label_text = ttk.Label(self.info_display_frame, text="Stats Drop:") dropped_label_text.grid(row=info_row, column=0, sticky=tk.W, padx=5, pady=1) self.dropped_entry = ttk.Entry( self.info_display_frame, textvariable=self.dropped_stats_var, state="readonly", width=30, ) self.dropped_entry.grid( row=info_row, column=1, sticky=tk.EW, padx=(0, 5), pady=1 ) incomplete_label_text = ttk.Label(self.info_display_frame, text="Incomplete:") incomplete_label_text.grid( row=info_row, column=2, sticky=tk.W, padx=(10, 2), pady=1 ) self.incomplete_entry = ttk.Entry( self.info_display_frame, textvariable=self.incomplete_stats_var, state="readonly", width=15, ) self.incomplete_entry.grid( row=info_row, column=3, columnspan=3, sticky=tk.EW, padx=(0, 5), pady=1 ) info_row += 1 # --- Row 5: "GE All" Button --- self.ge_all_button = ttk.Button( self.info_display_frame, text="GE All", command=self.app.go_to_all_gearth ) self.ge_all_button.grid( row=info_row, column=0, columnspan=6, sticky=tk.EW, padx=5, pady=(5, 5) ) # Configure column weights for Info Display frame self.info_display_frame.columnconfigure(1, weight=1) # Entry column self.info_display_frame.columnconfigure(3, weight=1) # Entry column self.info_display_frame.columnconfigure(4, weight=0) # Button Go self.info_display_frame.columnconfigure(5, weight=0) # Button GE logging.debug(f"{log_prefix} Info Display frame created.") # --- 5. Metadata Display Frame (Creation REMOVED from here) --- # This is now created and managed in ControlPanelApp # --- End of init_ui --- logging.debug(f"{log_prefix} init_ui widget creation complete.") # --- UI Update Methods --- def set_sar_center_coords(self, latitude_str: str, longitude_str: str): """Updates the SAR Center coordinates display.""" text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.sar_center_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR center coords: {e}") def set_mouse_coordinates(self, latitude_str: str, longitude_str: str): """Updates the SAR Mouse coordinates display.""" text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.mouse_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR mouse coords: {e}") def set_map_mouse_coordinates(self, latitude_str: str, longitude_str: str): """Updates the Map Mouse coordinates display.""" text = f"Lat={latitude_str}, Lon={longitude_str}" try: self.map_mouse_coords_var.set(text) except Exception as e: logging.warning(f"[UI Update] Error setting Map mouse coords: {e}") def set_sar_orientation(self, orientation_deg_str: str): """Updates the SAR Orientation display.""" try: self.sar_orientation_var.set(orientation_deg_str) except Exception as e: logging.warning(f"[UI Update] Error setting SAR orientation: {e}") def set_sar_size_km(self, size_text: str): """Updates the SAR Size display.""" try: self.sar_size_km_var.set(size_text) except Exception as e: logging.warning(f"[UI Update] Error setting SAR size: {e}") def set_statistics_display(self, dropped_text: str, incomplete_text: str): """Updates the statistics display fields.""" try: self.dropped_stats_var.set(dropped_text) self.incomplete_stats_var.set(incomplete_text) except Exception as e: logging.warning(f"[UI Update] Error setting stats display: {e}") def update_mfd_color_display( self, category_name: str, color_bgr_tuple: Tuple[int, int, int] ): """Updates the background color of an MFD category preview label.""" if category_name in self.mfd_color_labels: lbl = self.mfd_color_labels[category_name] # Format BGR tuple to hex color string #RRGGBB hex_color = ( f"#{color_bgr_tuple[2]:02x}" f"{color_bgr_tuple[1]:02x}" f"{color_bgr_tuple[0]:02x}" ) try: # Update label background if it still exists if lbl.winfo_exists(): lbl.config(background=hex_color) except Exception as e: logging.exception( f"[UI Update] Error updating MFD color for {category_name}: {e}" ) # --- StatusBar Class --- class StatusBar(ttk.Label): """Represents the status bar at the bottom of the main window.""" def __init__(self, parent: tk.Widget, *args, **kwargs): log_prefix = "[UI Setup]" logging.debug(f"{log_prefix} Initializing StatusBar...") super().__init__( parent, text=config.INITIAL_STATUS_MESSAGE, relief=tk.SUNKEN, anchor=tk.W, # Anchor text to the West (left) padding=(5, 2), # Add some internal padding *args, **kwargs, ) # Packed by the main application layout manager def set_status_text(self, text: str): """Sets the text displayed in the status bar.""" try: # Update text only if the widget still exists if self.winfo_exists(): self.config(text=text) except Exception: # Ignore errors during status update, especially during shutdown pass # --- Window Creation Helper --- def create_main_window( title: str, min_width: int, min_height: int, x_pos: int, y_pos: int ) -> tk.Tk: """Creates and configures the main Tkinter root window.""" log_prefix = "[UI Setup]" try: # Create the root window root = tk.Tk() # Set window title root.title(title) # Set minimum size constraint root.minsize(min_width, min_height) # Set initial position (may be overridden by OS window manager) # This is less critical now as the app constructor sets the final position root.geometry(f"+{x_pos}+{y_pos}") logging.debug( f"{log_prefix} Main Tkinter root window created (initial requested pos: {x_pos},{y_pos})." ) return root except Exception as e: logging.critical( f"{log_prefix} Failed to create main Tkinter window:", exc_info=True ) # Re-raise the exception to halt execution if window creation fails raise # --- END OF FILE ui.py ---