1#![deny(missing_docs)]
2
3use std::sync::atomic::AtomicU64;
10
11use floem_reactive::{SignalWith, UpdaterEffect};
12use peniko::kurbo::{Point, Size, Vec2};
13use winit::window::WindowId;
14use winit::window::{ResizeDirection, Theme};
15
16use crate::IntoView;
17use crate::platform::{Duration, Instant};
18
19use crate::{
20 app::{AppUpdateEvent, add_app_update_event},
21 message::{UPDATE_MESSAGES, UpdateMessage},
22 platform::menu::Menu,
23 view::View,
24 view::ViewId,
25 views::Decorators,
26 window::handle::{get_current_view, set_current_view},
27 window::tracking::with_window,
28};
29
30#[cfg(not(target_arch = "wasm32"))]
31pub use crate::platform::file_action::*;
32
33pub(crate) fn add_update_message(msg: UpdateMessage) {
35 let current_view = get_current_view();
36 let _ = UPDATE_MESSAGES.try_with(|msgs| {
37 let mut msgs = msgs.borrow_mut();
38 msgs.entry(current_view).or_default().push(msg);
39 });
40}
41
42pub fn toggle_window_maximized() {
44 add_update_message(UpdateMessage::ToggleWindowMaximized);
45}
46
47pub fn set_window_maximized(maximized: bool) {
49 add_update_message(UpdateMessage::SetWindowMaximized(maximized));
50}
51
52pub fn minimize_window() {
54 add_update_message(UpdateMessage::MinimizeWindow);
55}
56
57pub fn drag_window() {
59 add_update_message(UpdateMessage::DragWindow);
60}
61
62pub fn drag_resize_window(direction: ResizeDirection) {
64 add_update_message(UpdateMessage::DragResizeWindow(direction));
65}
66
67pub fn set_window_delta(delta: Vec2) {
69 add_update_message(UpdateMessage::SetWindowDelta(delta));
70}
71
72pub fn set_window_scale(window_scale: f64) {
76 add_update_message(UpdateMessage::WindowScale(window_scale));
77}
78
79pub fn inspect() {
81 add_update_message(UpdateMessage::Inspect);
82}
83
84pub fn set_global_theme(theme: Theme) {
88 add_app_update_event(AppUpdateEvent::ThemeChanged { theme });
89}
90
91pub fn set_theme(theme: Option<Theme>) {
95 add_update_message(UpdateMessage::SetTheme(theme));
96}
97
98pub fn toggle_global_theme() {
100 let theme = current_theme().unwrap_or(Theme::Dark);
101 let theme = match theme {
102 Theme::Light => Theme::Dark,
103 Theme::Dark => Theme::Light,
104 };
105 add_app_update_event(AppUpdateEvent::ThemeChanged { theme });
106}
107
108pub fn toggle_window_theme() {
110 let theme = current_theme().unwrap_or(Theme::Dark);
111 let theme = match theme {
112 Theme::Light => Theme::Dark,
113 Theme::Dark => Theme::Light,
114 };
115 add_update_message(UpdateMessage::SetTheme(Some(theme)));
117}
118
119pub fn current_theme() -> Option<Theme> {
121 let win_id = get_current_view().window_id()?;
122 with_window(&win_id, |w| w.theme())?
123}
124
125pub(crate) struct Timer {
126 pub(crate) token: TimerToken,
127 pub(crate) action: Box<dyn FnOnce(TimerToken)>,
128 pub(crate) deadline: Instant,
129 pub(crate) is_animation: bool,
130 pub(crate) window_id: Option<WindowId>,
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
136pub struct TimerToken(u64);
137
138impl TimerToken {
139 pub const INVALID: TimerToken = TimerToken(0);
141
142 pub fn next() -> TimerToken {
144 static TIMER_COUNTER: AtomicU64 = AtomicU64::new(0);
145 TimerToken(TIMER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
146 }
147
148 pub const fn from_raw(id: u64) -> TimerToken {
150 TimerToken(id)
151 }
152
153 pub const fn into_raw(self) -> u64 {
155 self.0
156 }
157
158 pub fn cancel(self) {
160 add_app_update_event(AppUpdateEvent::CancelTimer { timer: self });
161 }
162}
163
164pub fn exec_after(duration: Duration, action: impl FnOnce(TimerToken) + 'static) -> TimerToken {
166 let view = get_current_view();
167 let action = move |token| {
168 let current_view = get_current_view();
169 set_current_view(view.root());
170 action(token);
171 set_current_view(current_view);
172 };
173
174 let token = TimerToken::next();
175 let deadline = Instant::now() + duration;
176 add_app_update_event(AppUpdateEvent::RequestTimer {
177 timer: Timer {
178 token,
179 action: Box::new(action),
180 deadline,
181 is_animation: false,
182 window_id: None,
183 },
184 });
185 token
186}
187
188pub fn exec_after_animation_frame(action: impl FnOnce(TimerToken) + 'static) -> TimerToken {
193 let view = get_current_view();
194 let Some(window_id) = view.window_id() else {
195 return TimerToken::INVALID;
196 };
197
198 let action = move |token| {
199 let current_view = get_current_view();
200 set_current_view(view.root());
201 action(token);
202 set_current_view(current_view);
203 };
204
205 let token = TimerToken::next();
206 add_app_update_event(AppUpdateEvent::RequestAnimationTimer {
207 timer: Timer {
208 token,
209 action: Box::new(action),
210 deadline: Instant::now(), is_animation: true,
212 window_id: Some(window_id),
213 },
214 window_id,
215 });
216 token
217}
218
219pub fn debounce_action<T, F>(signal: impl SignalWith<T> + 'static, duration: Duration, action: F)
224where
225 T: std::hash::Hash + 'static,
226 F: Fn() + Clone + 'static,
227{
228 UpdaterEffect::new_stateful(
229 move |prev_opt: Option<(u64, Option<TimerToken>)>| {
230 use std::hash::Hasher;
231 let mut hasher = std::hash::DefaultHasher::new();
232 signal.with(|v| v.hash(&mut hasher));
233 let hash = hasher.finish();
234 let execute = prev_opt
235 .map(|(prev_hash, _)| prev_hash != hash)
236 .unwrap_or(true);
237 (execute, (hash, prev_opt.and_then(|(_, timer)| timer)))
238 },
239 move |execute, (hash, prev_timer): (u64, Option<TimerToken>)| {
240 if let Some(timer) = prev_timer {
242 timer.cancel();
243 }
244 let timer_token = if execute {
245 let action = action.clone();
246 Some(exec_after(duration, move |_| {
247 action();
248 }))
249 } else {
250 None
251 };
252 (hash, timer_token)
253 },
254 );
255}
256
257pub fn show_context_menu(menu: Menu, pos: Option<Point>) {
264 add_update_message(UpdateMessage::ShowContextMenu { menu, pos });
265}
266
267#[cfg(not(target_arch = "wasm32"))]
275pub fn set_window_menu(menu: Menu) {
276 add_update_message(UpdateMessage::WindowMenu { menu });
277}
278
279pub fn set_window_title(title: String) {
281 add_update_message(UpdateMessage::SetWindowTitle { title });
282}
283
284pub fn clear_focus() {
286 add_update_message(UpdateMessage::ClearFocus);
287}
288
289pub fn focus_window() {
291 add_update_message(UpdateMessage::FocusWindow);
292}
293
294pub fn set_ime_allowed(allowed: bool) {
296 add_update_message(UpdateMessage::SetImeAllowed { allowed });
297}
298
299pub fn set_ime_cursor_area(position: Point, size: Size) {
301 add_update_message(UpdateMessage::SetImeCursorArea { position, size });
302}
303
304pub fn add_overlay<V: View + 'static>(view: V) -> ViewId {
306 let view = view.style(move |s| s.absolute()).into_any();
307 let id = view.id();
308
309 add_update_message(UpdateMessage::AddOverlay { view });
310 id
311}
312
313pub fn remove_overlay(id: ViewId) {
315 add_update_message(UpdateMessage::RemoveOverlay { id });
316}