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::{ResizeDirection, Theme};
14
15use crate::platform::{Duration, Instant};
16
17use crate::{
18 app::{AppUpdateEvent, add_app_update_event},
19 message::{UPDATE_MESSAGES, UpdateMessage},
20 platform::menu::Menu,
21 view::View,
22 view::ViewId,
23 views::Decorators,
24 window::handle::{get_current_view, set_current_view},
25 window::tracking::with_window,
26};
27
28#[cfg(not(target_arch = "wasm32"))]
29pub use crate::platform::file_action::*;
30
31pub(crate) fn add_update_message(msg: UpdateMessage) {
33 let current_view = get_current_view();
34 let _ = UPDATE_MESSAGES.try_with(|msgs| {
35 let mut msgs = msgs.borrow_mut();
36 msgs.entry(current_view).or_default().push(msg);
37 });
38}
39
40pub fn toggle_window_maximized() {
42 add_update_message(UpdateMessage::ToggleWindowMaximized);
43}
44
45pub fn set_window_maximized(maximized: bool) {
47 add_update_message(UpdateMessage::SetWindowMaximized(maximized));
48}
49
50pub fn minimize_window() {
52 add_update_message(UpdateMessage::MinimizeWindow);
53}
54
55pub fn drag_window() {
57 add_update_message(UpdateMessage::DragWindow);
58}
59
60pub fn drag_resize_window(direction: ResizeDirection) {
62 add_update_message(UpdateMessage::DragResizeWindow(direction));
63}
64
65pub fn set_window_delta(delta: Vec2) {
67 add_update_message(UpdateMessage::SetWindowDelta(delta));
68}
69
70pub fn set_window_scale(window_scale: f64) {
74 add_update_message(UpdateMessage::WindowScale(window_scale));
75}
76
77pub fn inspect() {
79 add_update_message(UpdateMessage::Inspect);
80}
81
82pub fn set_global_theme(theme: Theme) {
86 add_app_update_event(AppUpdateEvent::ThemeChanged { theme });
87}
88
89pub fn set_theme(theme: Option<Theme>) {
93 add_update_message(UpdateMessage::SetTheme(theme));
94}
95
96pub fn toggle_global_theme() {
98 let theme = current_theme().unwrap_or(Theme::Dark);
99 let theme = match theme {
100 Theme::Light => Theme::Dark,
101 Theme::Dark => Theme::Light,
102 };
103 add_app_update_event(AppUpdateEvent::ThemeChanged { theme });
104}
105
106pub fn toggle_window_theme() {
108 let theme = current_theme().unwrap_or(Theme::Dark);
109 let theme = match theme {
110 Theme::Light => Theme::Dark,
111 Theme::Dark => Theme::Light,
112 };
113 add_update_message(UpdateMessage::SetTheme(Some(theme)));
115}
116
117pub fn current_theme() -> Option<Theme> {
119 let win_id = get_current_view().window_id()?;
120 with_window(&win_id, |w| w.theme())?
121}
122
123pub(crate) struct Timer {
124 pub(crate) token: TimerToken,
125 pub(crate) action: Box<dyn FnOnce(TimerToken)>,
126 pub(crate) deadline: Instant,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Hash)]
132pub struct TimerToken(u64);
133
134impl TimerToken {
135 pub const INVALID: TimerToken = TimerToken(0);
137
138 pub fn next() -> TimerToken {
140 static TIMER_COUNTER: AtomicU64 = AtomicU64::new(0);
141 TimerToken(TIMER_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed))
142 }
143
144 pub const fn from_raw(id: u64) -> TimerToken {
146 TimerToken(id)
147 }
148
149 pub const fn into_raw(self) -> u64 {
151 self.0
152 }
153
154 pub fn cancel(self) {
156 add_app_update_event(AppUpdateEvent::CancelTimer { timer: self });
157 }
158}
159
160pub fn exec_after(duration: Duration, action: impl FnOnce(TimerToken) + 'static) -> TimerToken {
162 let view = get_current_view();
163 let action = move |token| {
164 let current_view = get_current_view();
165 set_current_view(view);
166 action(token);
167 set_current_view(current_view);
168 };
169
170 let token = TimerToken::next();
171 let deadline = Instant::now() + duration;
172 add_app_update_event(AppUpdateEvent::RequestTimer {
173 timer: Timer {
174 token,
175 action: Box::new(action),
176 deadline,
177 },
178 });
179 token
180}
181
182pub fn debounce_action<T, F>(signal: impl SignalWith<T> + 'static, duration: Duration, action: F)
187where
188 T: std::hash::Hash + 'static,
189 F: Fn() + Clone + 'static,
190{
191 UpdaterEffect::new_stateful(
192 move |prev_opt: Option<(u64, Option<TimerToken>)>| {
193 use std::hash::Hasher;
194 let mut hasher = std::hash::DefaultHasher::new();
195 signal.with(|v| v.hash(&mut hasher));
196 let hash = hasher.finish();
197 let execute = prev_opt
198 .map(|(prev_hash, _)| prev_hash != hash)
199 .unwrap_or(true);
200 (execute, (hash, prev_opt.and_then(|(_, timer)| timer)))
201 },
202 move |execute, (hash, prev_timer): (u64, Option<TimerToken>)| {
203 if let Some(timer) = prev_timer {
205 timer.cancel();
206 }
207 let timer_token = if execute {
208 let action = action.clone();
209 Some(exec_after(duration, move |_| {
210 action();
211 }))
212 } else {
213 None
214 };
215 (hash, timer_token)
216 },
217 );
218}
219
220pub fn show_context_menu(menu: Menu, pos: Option<Point>) {
227 add_update_message(UpdateMessage::ShowContextMenu { menu, pos });
228}
229
230#[cfg(not(target_arch = "wasm32"))]
238pub fn set_window_menu(menu: Menu) {
239 add_update_message(UpdateMessage::WindowMenu { menu });
240}
241
242pub fn set_window_title(title: String) {
244 add_update_message(UpdateMessage::SetWindowTitle { title });
245}
246
247pub fn focus_window() {
249 add_update_message(UpdateMessage::FocusWindow);
250}
251
252pub fn clear_app_focus() {
254 add_update_message(UpdateMessage::ClearAppFocus);
255}
256
257pub fn set_ime_allowed(allowed: bool) {
259 add_update_message(UpdateMessage::SetImeAllowed { allowed });
260}
261
262pub fn set_ime_cursor_area(position: Point, size: Size) {
264 add_update_message(UpdateMessage::SetImeCursorArea { position, size });
265}
266
267pub fn add_overlay<V: View + 'static>(view: V) -> ViewId {
269 let id = view.id();
270 let view = view.style(move |s| s.absolute());
271
272 add_update_message(UpdateMessage::AddOverlay {
273 view: Box::new(view),
274 });
275 id
276}
277
278pub fn remove_overlay(id: ViewId) {
280 add_update_message(UpdateMessage::RemoveOverlay { id });
281}