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
18pub struct AppState {
20 pub(crate) focus: Option<ViewId>,
22 pub(crate) prev_focus: Option<ViewId>,
23 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 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 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 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 pub fn schedule_style(&mut self, id: ViewId) {
210 self.scheduled_updates.push(FrameUpdate::Style(id));
211 }
212
213 pub fn schedule_layout(&mut self, id: ViewId) {
216 self.scheduled_updates.push(FrameUpdate::Layout(id));
217 }
218
219 pub fn schedule_paint(&mut self, id: ViewId) {
222 self.scheduled_updates.push(FrameUpdate::Paint(id));
223 }
224
225 pub fn request_compute_layout_recursive(&mut self, _id: ViewId) {
227 self.request_compute_layout = true;
228 }
229
230 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 return;
240 }
241 self.active = Some(id);
242
243 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 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 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 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}