diff --git a/src/optiverse/core/raytracing_math.py b/src/optiverse/core/raytracing_math.py index eb3acae3..6601edf9 100644 --- a/src/optiverse/core/raytracing_math.py +++ b/src/optiverse/core/raytracing_math.py @@ -12,8 +12,12 @@ NUMBA_AVAILABLE = True except ImportError: - # Fallback: no-op decorator if numba isn't available - def jit(*args, **kwargs): + # Fallback: no-op decorator if numba isn't available. mypy flags the + # redefinition differently depending on whether numba is importable in the + # analysis environment (no-redef when it is, misc when it isn't), so both + # codes are suppressed for cross-platform CI. warn_unused_ignores is off, + # so the code that doesn't apply on a given platform is harmless. + def jit(*args, **kwargs): # type: ignore[no-redef,misc] def decorator(func): return func @@ -561,7 +565,7 @@ def compute_dichroic_reflectance( def refract_vector_snell( v_in: np.ndarray, n_hat: np.ndarray, n1: float, n2: float -) -> tuple[np.ndarray | None, bool]: +) -> tuple[np.ndarray, bool]: """ Apply Snell's law to refract a ray at an interface. @@ -572,9 +576,9 @@ def refract_vector_snell( n2: Refractive index of transmitted medium Returns: - Tuple of (refracted_direction, is_total_reflection) - - refracted_direction: Refracted ray direction (normalized), - or None if total internal reflection + Tuple of (direction, is_total_reflection) + - direction: Refracted ray direction (normalized), or the reflected + direction if total internal reflection occurs - is_total_reflection: True if total internal reflection occurs Physics: diff --git a/src/optiverse/objects/definitions_loader.py b/src/optiverse/objects/definitions_loader.py index aa826908..8410803e 100644 --- a/src/optiverse/objects/definitions_loader.py +++ b/src/optiverse/objects/definitions_loader.py @@ -25,12 +25,17 @@ def _iter_component_json_files(library_path: Path | None = None) -> list[Path]: library_path: Optional custom library path. If None, uses built-in library. Returns: - List of Path objects to component folders + List of Path objects to component folders, sorted by folder name for a + deterministic order across platforms (iterdir() order is filesystem + dependent and differs between Linux/Windows/macOS). """ root = library_path if library_path else _library_root() if not root.exists(): return [] - return [p for p in root.iterdir() if p.is_dir() and (p / "component.json").exists()] + folders = [ + p for p in root.iterdir() if p.is_dir() and (p / "component.json").exists() + ] + return sorted(folders, key=lambda p: p.name) def load_component_records( diff --git a/src/optiverse/ui/theme_manager.py b/src/optiverse/ui/theme_manager.py index 5a504f65..0f6819a9 100644 --- a/src/optiverse/ui/theme_manager.py +++ b/src/optiverse/ui/theme_manager.py @@ -208,6 +208,31 @@ def _create_light_palette() -> QtGui.QPalette: return palette +def _set_color_scheme(dark_mode: bool) -> None: + """ + Pin Qt's color scheme so native styling matches the applied theme. + + Uses ``QStyleHints.setColorScheme`` (Qt 6.8+). On older Qt versions the + setter is unavailable and this is a no-op - the QSS/palette still apply, + only the native-chrome override (e.g. Windows dark-mode menu bar) is + unavailable to correct. + + Args: + dark_mode: True to request the dark color scheme, False for light. + """ + style_hints = QtGui.QGuiApplication.styleHints() + setter = getattr(style_hints, "setColorScheme", None) + if setter is None: + return + scheme = ( + QtCore.Qt.ColorScheme.Dark if dark_mode else QtCore.Qt.ColorScheme.Light + ) + try: + setter(scheme) + except (AttributeError, TypeError) as e: + _logger.debug("Could not set color scheme: %s", e) + + def apply_theme(dark_mode: bool) -> None: """ Apply the appropriate theme (stylesheet + palette) based on dark mode setting. @@ -225,6 +250,16 @@ def apply_theme(dark_mode: bool) -> None: _logger.warning("No QApplication instance - cannot apply theme") return + # Pin Qt's color scheme to match the app theme. + # + # On Windows 11, Qt's native style paints native chrome (most visibly the + # QMenuBar's top-level items) using the OS color scheme, ignoring the QSS + # `color` and the application palette. When the OS is in dark mode but the + # app is in light mode, this renders white menu-bar text on the light + # menu-bar background - i.e. invisible. Forcing the color scheme keeps the + # native rendering consistent with the theme we actually applied. + _set_color_scheme(dark_mode) + # Apply stylesheet and palette if dark_mode: app.setStyleSheet(get_dark_stylesheet())