Skip to main content

native_theme/resolve/
context.rs

1//! Resolution-time inputs (font DPI, button order, icon theme) captured
2//! once per theme-build and passed by reference through the pipeline.
3//!
4//! Per `docs/archive/v0.5.7_gaps.md` §G7:
5//! intentionally NO `impl Default` — runtime-detected types must signal
6//! intent at the call site. Use [`ResolutionContext::from_system`] for
7//! production, or [`ResolutionContext::for_tests`] for deterministic
8//! test values.
9
10use std::borrow::Cow;
11
12use crate::model::DialogButtonOrder;
13
14/// Resolution-time inputs captured once per theme-build.
15///
16/// Replaces the `font_dpi: Option<f32>` parameter on
17/// [`ThemeMode::into_resolved`](crate::theme::ThemeMode::into_resolved) and
18/// the corresponding internal `OverlaySource` field.
19///
20/// Accessibility preferences live on
21/// [`SystemTheme`](crate::SystemTheme), NOT here — accessibility is a
22/// render-time concern, not a resolve-time concern
23/// (`docs/archive/v0.5.7_gaps.md` §G7).
24///
25/// # Examples
26///
27/// ```
28/// use native_theme::resolve::ResolutionContext;
29///
30/// // Production: auto-detect from OS.
31/// let ctx = ResolutionContext::from_system();
32/// assert!(ctx.font_dpi > 0.0);
33///
34/// // Tests: deterministic values.
35/// let ctx = ResolutionContext::for_tests();
36/// assert_eq!(ctx.font_dpi, 96.0);
37/// assert!(ctx.icon_theme.is_none());
38/// ```
39#[derive(Clone, Debug)]
40pub struct ResolutionContext {
41    /// Font DPI for pt-to-px conversion.
42    ///
43    /// [`from_system`](Self::from_system) detects it: 72.0 on macOS, 96.0 on
44    /// Windows, and on Linux KDE's `forceFontDPI` (with the `kde` feature),
45    /// then `Xft.dpi`, then xrandr's physical DPI (with `kde` or `portal`),
46    /// then 96.0; 96.0 elsewhere. When the OS theme is read
47    /// ([`SystemTheme::from_system`](crate::SystemTheme::from_system)), the
48    /// reader's own DPI replaces it: KDE's `forceFontDPI` → `Xft.dpi` →
49    /// xrandr → 96.0, GNOME's `Xft.dpi` → xrandr → 96.0, macOS's 72.0 and
50    /// Windows' 96.0.
51    pub font_dpi: f32,
52    /// Dialog button ordering (`PrimaryLeft` on KDE and Windows,
53    /// `PrimaryRight` elsewhere).
54    pub button_order: DialogButtonOrder,
55    /// Runtime-detected icon theme name, used when the preset and
56    /// per-variant `icon_theme` fields are both `None`. Three-tier
57    /// precedence in the pipeline: per-variant → `Theme`-level shared →
58    /// this runtime detection.
59    ///
60    /// [`from_system`](Self::from_system) puts the detected system icon
61    /// theme here, or `None` where detection failed;
62    /// [`system_icon_theme()`](crate::theme::system_icon_theme) gives the
63    /// reason.
64    pub icon_theme: Option<Cow<'static, str>>,
65}
66
67impl ResolutionContext {
68    /// Build the context by auto-detecting from the current OS.
69    ///
70    /// Calls:
71    /// - `crate::detect::system_font_dpi` for `font_dpi` (private)
72    /// - `crate::resolve::inheritance::platform_button_order` for
73    ///   `button_order`
74    /// - [`crate::model::icons::system_icon_theme`] for `icon_theme`, which
75    ///   is `None` where detection fails
76    #[must_use]
77    pub fn from_system() -> Self {
78        Self::with_detected_icon_theme(crate::model::icons::system_icon_theme())
79    }
80
81    /// [`from_system`](Self::from_system), with `detected` as the outcome
82    /// of the icon theme's detection.
83    pub(crate) fn with_detected_icon_theme(detected: crate::Result<String>) -> Self {
84        Self {
85            font_dpi: crate::detect::system_font_dpi(),
86            button_order: crate::resolve::inheritance::platform_button_order(),
87            icon_theme: detected.ok().map(Cow::Owned),
88        }
89    }
90
91    /// Deterministic values for tests: 96 DPI, `PrimaryRight` button
92    /// order, no detected `icon_theme`.
93    ///
94    /// Tests that need a specific DPI (e.g. 72.0 for Apple point→pixel)
95    /// can construct the struct via a literal:
96    /// `ResolutionContext { font_dpi: 72.0, ..ResolutionContext::for_tests() }`.
97    #[must_use]
98    pub fn for_tests() -> Self {
99        Self {
100            font_dpi: 96.0,
101            button_order: DialogButtonOrder::PrimaryRight,
102            icon_theme: None,
103        }
104    }
105}
106
107// Intentionally NO `impl Default for ResolutionContext`.
108// See module-level doc comment for the signal-intent rationale.