floem/
app_state.rs

1use std::collections::{HashMap, HashSet};
2
3use floem_winit::window::CursorIcon;
4use peniko::kurbo::{Point, Size};
5use taffy::{AvailableSpace, NodeId};
6
7use crate::{
8    context::{DragState, FrameUpdate, InteractionState},
9    event::{Event, EventListener},
10    id::ViewId,
11    inspector::CaptureState,
12    menu::Menu,
13    responsive::{GridBreakpoints, ScreenSizeBp},
14    style::{CursorStyle, Style, StyleClassRef, StyleSelector},
15    view_storage::VIEW_STORAGE,
16};
17
18/// Encapsulates and owns the global state of the application,
19pub struct AppState {
20    /// keyboard focus
21    pub(crate) focus: Option<ViewId>,
22    pub(crate) prev_focus: Option<ViewId>,
23    /// when a view is active, it gets mouse event even when the mouse is
24    /// not on it
25    pub(crate) active: Option<ViewId>,
26    pub(crate) root_view_id: ViewId,
27    pub(crate) root: Option<NodeId>,
28    pub(crate) root_size: Size,
29    pub(crate) scale: f64,
30    pub(crate) scheduled_updates: Vec<FrameUpdate>,
31    pub(crate) request_compute_layout: bool,
32    pub(crate) request_paint: bool,
33    pub(crate) disabled: HashSet<ViewId>,
34    pub(crate) keyboard_navigable: HashSet<ViewId>,
35    pub(crate) draggable: HashSet<ViewId>,
36    pub(crate) dragging: Option<DragState>,
37    pub(crate) drag_start: Option<(ViewId, Point)>,
38    pub(crate) dragging_over: HashSet<ViewId>,
39    pub(crate) screen_size_bp: ScreenSizeBp,
40    pub(crate) grid_bps: GridBreakpoints,
41    pub(crate) clicking: HashSet<ViewId>,
42    pub(crate) hovered: HashSet<ViewId>,
43    /// This keeps track of all views that have an animation,
44    /// regardless of the status of the animation
45    pub(crate) cursor: Option<CursorStyle>,
46    pub(crate) last_cursor: CursorIcon,
47    pub(crate) last_cursor_location: Point,
48    pub(crate) keyboard_navigation: bool,
49    pub(crate) window_menu: HashMap<usize, Box<dyn Fn()>>,
50    pub(crate) context_menu: HashMap<usize, Box<dyn Fn()>>,
51
52    /// This is set if we're currently capturing the window for the inspector.
53    pub(crate) capture: Option<CaptureState>,
54}
55
56impl AppState {
57    pub fn new(root_view_id: ViewId) -> Self {
58        Self {
59            root: None,
60            root_view_id,
61            focus: None,
62            prev_focus: None,
63            active: None,
64            scale: 1.0,
65            root_size: Size::ZERO,
66            screen_size_bp: ScreenSizeBp::Xs,
67            scheduled_updates: Vec::new(),
68            request_paint: false,
69            request_compute_layout: false,
70            disabled: HashSet::new(),
71            keyboard_navigable: HashSet::new(),
72            draggable: HashSet::new(),
73            dragging: None,
74            drag_start: None,
75            dragging_over: HashSet::new(),
76            clicking: HashSet::new(),
77            hovered: HashSet::new(),
78            cursor: None,
79            last_cursor: CursorIcon::Default,
80            last_cursor_location: Default::default(),
81            keyboard_navigation: false,
82            grid_bps: GridBreakpoints::default(),
83            window_menu: HashMap::new(),
84            context_menu: HashMap::new(),
85            capture: None,
86        }
87    }
88
89    /// This removes a view from the app state.
90    pub fn remove_view(&mut self, id: ViewId) {
91        let exists = VIEW_STORAGE.with_borrow(|s| s.view_ids.contains_key(id));
92        if !exists {
93            return;
94        }
95
96        let children = id.children();
97        for child in children {
98            self.remove_view(child);
99        }
100        let view_state = id.state();
101
102        let cleanup_listener = view_state.borrow().cleanup_listener.clone();
103        if let Some(action) = cleanup_listener {
104            action();
105        }
106
107        let node = view_state.borrow().node;
108        let taffy = id.taffy();
109        let mut taffy = taffy.borrow_mut();
110
111        let children = taffy.children(node);
112        if let Ok(children) = children {
113            for child in children {
114                let _ = taffy.remove(child);
115            }
116        }
117        let _ = taffy.remove(node);
118        id.remove();
119        self.disabled.remove(&id);
120        self.keyboard_navigable.remove(&id);
121        self.draggable.remove(&id);
122        self.dragging_over.remove(&id);
123        self.clicking.remove(&id);
124        self.hovered.remove(&id);
125        self.clicking.remove(&id);
126        if self.focus == Some(id) {
127            self.focus = None;
128        }
129        if self.prev_focus == Some(id) {
130            self.prev_focus = None;
131        }
132
133        if self.active == Some(id) {
134            self.active = None;
135        }
136    }
137
138    pub(crate) fn can_focus(&self, id: ViewId) -> bool {
139        self.keyboard_navigable.contains(&id) && !self.is_disabled(&id) && !id.is_hidden_recursive()
140    }
141
142    pub fn is_hovered(&self, id: &ViewId) -> bool {
143        self.hovered.contains(id)
144    }
145
146    pub fn is_disabled(&self, id: &ViewId) -> bool {
147        self.disabled.contains(id)
148    }
149
150    pub fn is_focused(&self, id: &ViewId) -> bool {
151        self.focus.map(|f| &f == id).unwrap_or(false)
152    }
153
154    pub fn is_active(&self, id: &ViewId) -> bool {
155        self.active.map(|a| &a == id).unwrap_or(false)
156    }
157
158    pub fn is_clicking(&self, id: &ViewId) -> bool {
159        self.clicking.contains(id)
160    }
161
162    pub fn is_dragging(&self) -> bool {
163        self.dragging
164            .as_ref()
165            .map(|d| d.released_at.is_none())
166            .unwrap_or(false)
167    }
168
169    pub fn set_root_size(&mut self, size: Size) {
170        self.root_size = size;
171        self.compute_layout();
172    }
173
174    #[allow(clippy::too_many_arguments)]
175    pub(crate) fn compute_style(
176        &mut self,
177        view_id: ViewId,
178        view_style: Option<Style>,
179        view_interact_state: InteractionState,
180        view_class: Option<StyleClassRef>,
181        context: &Style,
182    ) -> bool {
183        let screen_size_bp = self.screen_size_bp;
184        let view_state = view_id.state();
185        let request_new_frame = view_state.borrow_mut().compute_style(
186            view_style,
187            view_interact_state,
188            screen_size_bp,
189            view_class,
190            context,
191        );
192        request_new_frame
193    }
194
195    pub fn compute_layout(&mut self) {
196        if let Some(root) = self.root {
197            let _ = self.root_view_id.taffy().borrow_mut().compute_layout(
198                root,
199                taffy::prelude::Size {
200                    width: AvailableSpace::Definite((self.root_size.width / self.scale) as f32),
201                    height: AvailableSpace::Definite((self.root_size.height / self.scale) as f32),
202                },
203            );
204        }
205    }
206
207    /// Requests that the style pass will run for `id` on the next frame, and ensures new frame is
208    /// scheduled to happen.
209    pub fn schedule_style(&mut self, id: ViewId) {
210        self.scheduled_updates.push(FrameUpdate::Style(id));
211    }
212
213    /// Requests that the layout pass will run for `id` on the next frame, and ensures new frame is
214    /// scheduled to happen.
215    pub fn schedule_layout(&mut self, id: ViewId) {
216        self.scheduled_updates.push(FrameUpdate::Layout(id));
217    }
218
219    /// Requests that the paint pass will run for `id` on the next frame, and ensures new frame is
220    /// scheduled to happen.
221    pub fn schedule_paint(&mut self, id: ViewId) {
222        self.scheduled_updates.push(FrameUpdate::Paint(id));
223    }
224
225    /// Requests that `compute_layout` will run for `_id` and all direct and indirect children.
226    pub fn request_compute_layout_recursive(&mut self, _id: ViewId) {
227        self.request_compute_layout = true;
228    }
229
230    // `Id` is unused currently, but could be used to calculate damage regions.
231    pub fn request_paint(&mut self, _id: ViewId) {
232        self.request_paint = true;
233    }
234
235    pub(crate) fn update_active(&mut self, id: ViewId) {
236        if self.active.is_some() {
237            // the first update_active wins, so if there's active set,
238            // don't do anything.
239            return;
240        }
241        self.active = Some(id);
242
243        // To apply the styles of the Active selector
244        if self.has_style_for_sel(id, StyleSelector::Active) {
245            id.request_style();
246        }
247    }
248
249    pub(crate) fn update_screen_size_bp(&mut self, size: Size) {
250        let bp = self.grid_bps.get_width_bp(size.width);
251        self.screen_size_bp = bp;
252    }
253
254    pub(crate) fn clear_focus(&mut self) {
255        if let Some(old_id) = self.focus {
256            // To remove the styles applied by the Focus selector
257            if self.has_style_for_sel(old_id, StyleSelector::Focus)
258                || self.has_style_for_sel(old_id, StyleSelector::FocusVisible)
259            {
260                old_id.request_style();
261            }
262        }
263
264        if self.focus.is_some() {
265            self.prev_focus = self.focus;
266        }
267        self.focus = None;
268    }
269
270    pub(crate) fn update_focus(&mut self, id: ViewId, keyboard_navigation: bool) {
271        if self.focus.is_some() {
272            return;
273        }
274
275        self.focus = Some(id);
276        self.keyboard_navigation = keyboard_navigation;
277
278        if self.has_style_for_sel(id, StyleSelector::Focus)
279            || self.has_style_for_sel(id, StyleSelector::FocusVisible)
280        {
281            id.request_style();
282        }
283    }
284
285    pub(crate) fn has_style_for_sel(&mut self, id: ViewId, selector_kind: StyleSelector) -> bool {
286        let view_state = id.state();
287        let view_state = view_state.borrow();
288
289        view_state.has_style_selectors.has(selector_kind)
290            || (selector_kind == StyleSelector::Dragging && view_state.dragging_style.is_some())
291    }
292
293    pub(crate) fn update_context_menu(&mut self, menu: &mut Menu) {
294        if let Some(action) = menu.item.action.take() {
295            self.context_menu.insert(menu.item.id as usize, action);
296        }
297        for child in menu.children.iter_mut() {
298            match child {
299                crate::menu::MenuEntry::Separator => {}
300                crate::menu::MenuEntry::Item(item) => {
301                    if let Some(action) = item.action.take() {
302                        self.context_menu.insert(item.id as usize, action);
303                    }
304                }
305                crate::menu::MenuEntry::SubMenu(m) => {
306                    self.update_context_menu(m);
307                }
308            }
309        }
310    }
311
312    pub(crate) fn focus_changed(&mut self, old: Option<ViewId>, new: Option<ViewId>) {
313        if let Some(id) = new {
314            // To apply the styles of the Focus selector
315            if self.has_style_for_sel(id, StyleSelector::Focus)
316                || self.has_style_for_sel(id, StyleSelector::FocusVisible)
317            {
318                id.request_style_recursive();
319            }
320            id.apply_event(&EventListener::FocusGained, &Event::FocusGained);
321            id.scroll_to(None);
322        }
323
324        if let Some(old_id) = old {
325            // To remove the styles applied by the Focus selector
326            if self.has_style_for_sel(old_id, StyleSelector::Focus)
327                || self.has_style_for_sel(old_id, StyleSelector::FocusVisible)
328            {
329                old_id.request_style_recursive();
330            }
331            old_id.apply_event(&EventListener::FocusLost, &Event::FocusLost);
332        }
333    }
334}