ferroui_native/
native_platform_settings.rs1use crate::frn_string::frn_string_to_string;
2use crate::helpers::ComResultExt;
3use crate::interop::*;
4use ferroui_base::input::platform::PlatformHotkeyConfiguration;
5use ferroui_base::input::PointerType;
6use ferroui_base::media::Color;
7use ferroui_base::platform::{
8 ColorContrastPreference, DefaultPlatformSettings, IPlatformSettings, PlatformColorValues, PlatformThemeVariant,
9};
10use ferroui_base::reactive::IDisposable;
11use ferroui_base::Size;
12use ferroui_microcom::ComPtr;
13use std::cell::{Cell, RefCell};
14use std::rc::{Rc, Weak};
15use std::time::Duration;
16
17fn to_theme(theme: FrnPlatformThemeVariant) -> Option<(PlatformThemeVariant, ColorContrastPreference)> {
20 if theme == FrnPlatformThemeVariant::Dark {
21 Some((PlatformThemeVariant::Dark, ColorContrastPreference::NoPreference))
22 } else if theme == FrnPlatformThemeVariant::Light {
23 Some((PlatformThemeVariant::Light, ColorContrastPreference::NoPreference))
24 } else if theme == FrnPlatformThemeVariant::HighContrastDark {
25 Some((PlatformThemeVariant::Dark, ColorContrastPreference::High))
26 } else if theme == FrnPlatformThemeVariant::HighContrastLight {
27 Some((PlatformThemeVariant::Light, ColorContrastPreference::High))
28 } else {
29 None
30 }
31}
32
33fn to_color_values(theme: FrnPlatformThemeVariant, accent_color: u32) -> PlatformColorValues {
34 let Some((theme_variant, contrast_preference)) = to_theme(theme) else {
35 panic!("Specified argument was out of the range of valid values: {theme:?}");
36 };
37 let color_values =
38 PlatformColorValues::new().with_theme_variant(theme_variant).with_contrast_preference(contrast_preference);
39
40 if accent_color > 0 {
41 color_values.with_accent_color1(Color::from_uint32(accent_color))
42 } else {
43 color_values
44 }
45}
46
47pub struct NativePlatformSettings {
49 base: DefaultPlatformSettings,
50 platform_settings: ComPtr<IFrnPlatformSettings>,
51 color_values: Cell<Option<PlatformColorValues>>,
52 last_language: RefCell<Option<String>>,
53}
54
55struct ColorsChangeCallback {
56 settings: Weak<NativePlatformSettings>,
57}
58
59impl IFrnActionCallbackImpl for ColorsChangeCallback {
60 fn run(&self) {
61 crate::callback_base::guard((), || {
62 if let Some(settings) = self.settings.upgrade() {
63 settings.on_color_values_changed();
64 }
65 })
66 }
67}
68
69struct LanguageChangeCallback {
70 settings: Weak<NativePlatformSettings>,
71}
72
73impl IFrnActionCallbackImpl for LanguageChangeCallback {
74 fn run(&self) {
75 crate::callback_base::guard((), || {
76 if let Some(settings) = self.settings.upgrade() {
77 settings.on_preferred_language_changed();
78 }
79 })
80 }
81}
82
83impl NativePlatformSettings {
84 pub fn new(platform_settings: ComPtr<IFrnPlatformSettings>) -> Rc<NativePlatformSettings> {
85 let this = Rc::new(NativePlatformSettings {
86 base: DefaultPlatformSettings::new(),
87 platform_settings,
88 color_values: Cell::new(None),
89 last_language: RefCell::new(None),
90 });
91 let colors_change = IFrnActionCallback::from_impl(ColorsChangeCallback { settings: Rc::downgrade(&this) });
92 this.platform_settings.register_colors_change(Some(&colors_change));
93 let language_change =
94 IFrnActionCallback::from_impl(LanguageChangeCallback { settings: Rc::downgrade(&this) });
95 this.platform_settings.register_language_change(Some(&language_change));
96 this
97 }
98
99 fn get_uncached_color_values(&self) -> PlatformColorValues {
100 to_color_values(self.platform_settings.get_platform_theme(), self.platform_settings.get_accent_color())
101 }
102
103 pub fn on_color_values_changed(&self) {
104 let old_color_values = self.color_values.get();
105 let color_values = self.get_uncached_color_values();
106
107 if old_color_values != Some(color_values) {
108 self.color_values.set(Some(color_values));
109 self.base.on_color_values_changed(color_values);
110 }
111 }
112
113 fn on_preferred_language_changed(&self) {
114 let old_language = self.last_language.borrow_mut().take();
115
116 if old_language != Some(self.preferred_application_language()) {
117 self.base.on_preferred_application_language_changed();
118 }
119 }
120
121 fn query_preferred_application_language(&self) -> String {
122 let language = self.platform_settings.get_preferred_language().check();
123 match language.and_then(|language| frn_string_to_string(&language)) {
124 Some(value) if !value.is_empty() => value,
125 _ => self.base.preferred_application_language(),
126 }
127 }
128}
129
130impl IPlatformSettings for NativePlatformSettings {
131 fn get_tap_size(&self, type_: PointerType) -> Size {
132 self.base.get_tap_size(type_)
133 }
134
135 fn get_double_tap_size(&self, type_: PointerType) -> Size {
136 self.base.get_double_tap_size(type_)
137 }
138
139 fn get_double_tap_time(&self, type_: PointerType) -> Duration {
140 self.base.get_double_tap_time(type_)
141 }
142
143 fn hold_wait_duration(&self) -> Duration {
144 self.base.hold_wait_duration()
145 }
146
147 fn hotkey_configuration(&self) -> Rc<PlatformHotkeyConfiguration> {
148 self.base.hotkey_configuration()
149 }
150
151 fn preferred_application_language(&self) -> String {
152 if let Some(language) = self.last_language.borrow().clone() {
153 return language;
154 }
155 let language = self.query_preferred_application_language();
156 *self.last_language.borrow_mut() = Some(language.clone());
157 language
158 }
159
160 fn get_color_values(&self) -> PlatformColorValues {
161 match self.color_values.get() {
162 Some(color_values) => color_values,
163 None => {
164 let color_values = self.get_uncached_color_values();
165 self.color_values.set(Some(color_values));
166 color_values
167 }
168 }
169 }
170
171 fn color_values_changed(&self, handler: Rc<dyn Fn(&PlatformColorValues)>) -> Rc<dyn IDisposable> {
172 self.base.color_values_changed(handler)
173 }
174
175 fn preferred_application_language_changed(&self, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
176 self.base.preferred_application_language_changed(handler)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn themes() {
186 use ColorContrastPreference::*;
187 use PlatformThemeVariant::*;
188 assert_eq!(to_theme(FrnPlatformThemeVariant::Light), Some((Light, NoPreference)));
189 assert_eq!(to_theme(FrnPlatformThemeVariant::Dark), Some((Dark, NoPreference)));
190 assert_eq!(to_theme(FrnPlatformThemeVariant::HighContrastLight), Some((Light, High)));
191 assert_eq!(to_theme(FrnPlatformThemeVariant::HighContrastDark), Some((Dark, High)));
192 assert_eq!(to_theme(FrnPlatformThemeVariant(9)), None);
193 }
194
195 #[test]
196 fn accent_color_is_only_taken_when_the_system_reports_one() {
197 let values = to_color_values(FrnPlatformThemeVariant::HighContrastDark, 0xFF336699);
198 assert_eq!(values.theme_variant(), PlatformThemeVariant::Dark);
199 assert_eq!(values.contrast_preference(), ColorContrastPreference::High);
200 assert_eq!(values.accent_color1(), Color::from_uint32(0xFF336699));
201
202 let values = to_color_values(FrnPlatformThemeVariant::Light, 0);
203 assert_eq!(values, PlatformColorValues::new());
204 }
205
206 #[test]
209 fn native_settings_report_the_system_values() {
210 let factory = create_ferro_native().expect("factory");
211 let Ok(Some(native)) = factory.create_platform_settings() else {
212 eprintln!("skipped: the native platform settings are not available");
213 return;
214 };
215 let settings = NativePlatformSettings::new(native);
216
217 assert_eq!(settings.get_color_values(), settings.get_color_values());
219 assert_eq!(settings.preferred_application_language(), settings.preferred_application_language());
220 assert_eq!(settings.get_double_tap_time(PointerType::Mouse), Duration::from_millis(500));
221 }
222}