vizia_core/
environment.rs1use crate::prelude::*;
3
4use unic_langid::CharacterDirection;
5use unic_langid::LanguageIdentifier;
6
7#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
9pub enum ThemeMode {
10 #[default]
12 System,
13 DarkMode,
15 LightMode,
17}
18
19use crate::{context::EventContext, events::Event};
20
21pub struct Environment {
23 pub locale: Signal<LanguageIdentifier>,
25 pub direction: Signal<Direction>,
27 pub double_click_interval: Duration,
29 pub tooltip_delay: Duration,
31 pub theme_mode: ThemeMode,
33 pub system_theme_mode: ThemeMode,
35 pub(crate) caret_timer: Timer,
37}
38
39fn direction_from_locale(locale: &LanguageIdentifier) -> Direction {
40 match locale.character_direction() {
41 CharacterDirection::RTL => Direction::RightToLeft,
42 _ => Direction::LeftToRight,
43 }
44}
45
46fn apply_direction_class(cx: &mut EventContext, direction: Direction) {
47 let rtl = direction == Direction::RightToLeft;
48 let window_entities = cx.windows.keys().copied().collect::<Vec<_>>();
49
50 cx.with_current(Entity::root(), |cx| {
51 cx.toggle_class("rtl", rtl);
52 });
53
54 for window_entity in window_entities {
55 cx.with_current(window_entity, |cx| {
56 cx.toggle_class("rtl", rtl);
57 });
58 }
59}
60
61impl Environment {
62 pub(crate) fn new(cx: &mut Context) -> Self {
63 let locale: LanguageIdentifier =
64 sys_locale::get_locale().and_then(|l| l.parse().ok()).unwrap_or_default();
65 let caret_timer = cx.add_timer(Duration::from_millis(530), None, |cx, action| {
66 if matches!(action, TimerAction::Tick(_)) {
67 cx.emit(TextEvent::ToggleCaret);
68 }
69 });
70 let direction = direction_from_locale(&locale);
71 Self {
72 locale: Signal::new(locale.clone()),
73 direction: Signal::new(direction),
74 double_click_interval: Duration::from_millis(500),
75 tooltip_delay: Duration::from_millis(1500),
76 theme_mode: ThemeMode::default(),
77 system_theme_mode: ThemeMode::LightMode,
78 caret_timer,
79 }
80 }
81
82 pub fn effective_theme(&self) -> ThemeMode {
85 match self.theme_mode {
86 ThemeMode::System => self.system_theme_mode,
87 other => other,
88 }
89 }
90}
91
92pub enum EnvironmentEvent {
94 SetLocale(LanguageIdentifier),
96 SetDirection(Direction),
98 SetThemeMode(ThemeMode),
101 UseSystemLocale,
103 ToggleThemeMode,
105 SetDoubleClickInterval(Duration),
107 SetTooltipDelay(Duration),
109}
110
111impl Model for Environment {
112 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
113 event.take(|event, _| match event {
114 EnvironmentEvent::SetLocale(locale) => {
115 self.locale.set(locale.clone());
116 let direction = direction_from_locale(&locale);
117 self.direction.set(direction);
118 apply_direction_class(cx, direction);
119 cx.reload_styles().unwrap();
120 }
121
122 EnvironmentEvent::SetDirection(direction) => {
123 self.direction.set_if_changed(direction);
124 apply_direction_class(cx, direction);
125 cx.reload_styles().unwrap();
126 }
127
128 EnvironmentEvent::SetThemeMode(theme) => {
129 self.theme_mode = theme;
130 let is_dark = self.effective_theme() == ThemeMode::DarkMode;
131 cx.with_current(Entity::root(), |cx| {
132 cx.toggle_class("dark", is_dark);
133 });
134 cx.reload_styles().unwrap();
135 }
136
137 EnvironmentEvent::UseSystemLocale => {
138 let locale: LanguageIdentifier =
139 sys_locale::get_locale().map(|l| l.parse().unwrap()).unwrap_or_default();
140 let direction = direction_from_locale(&locale);
141 self.locale.set(locale);
142 self.direction.set(direction);
143 apply_direction_class(cx, direction);
144 cx.reload_styles().unwrap();
145 }
146
147 EnvironmentEvent::ToggleThemeMode => {
148 let theme_mode = match self.theme_mode {
149 ThemeMode::System => ThemeMode::System,
150 ThemeMode::DarkMode => ThemeMode::LightMode,
151 ThemeMode::LightMode => ThemeMode::DarkMode,
152 };
153
154 self.theme_mode = theme_mode;
155
156 let is_dark = self.effective_theme() == ThemeMode::DarkMode;
157 cx.with_current(Entity::root(), |cx| {
158 cx.toggle_class("dark", is_dark);
159 });
160
161 cx.reload_styles().unwrap();
162 }
163
164 EnvironmentEvent::SetDoubleClickInterval(interval) => {
165 self.double_click_interval = interval;
166 }
167
168 EnvironmentEvent::SetTooltipDelay(delay) => {
169 self.tooltip_delay = delay;
170 }
171 });
172
173 event.map(|event, _| match event {
174 WindowEvent::ThemeChanged(theme) => {
175 self.system_theme_mode = *theme;
176 if self.theme_mode == ThemeMode::System {
177 let is_dark = self.system_theme_mode == ThemeMode::DarkMode;
178 cx.with_current(Entity::root(), |cx| {
179 cx.toggle_class("dark", is_dark);
180 });
181 cx.reload_styles().unwrap();
182 }
183 }
184 _ => (),
185 })
186 }
187}