floem/
view_state.rs

1use crate::{
2    animate::Animation,
3    context::{
4        EventCallback, InteractionState, MenuCallback, MoveListener, ResizeCallback, ResizeListener,
5    },
6    event::EventListener,
7    pointer::PointerInputEvent,
8    prop_extractor,
9    responsive::ScreenSizeBp,
10    style::{
11        Background, BorderColor, BorderRadius, BoxShadowProp, LayoutProps, Outline, OutlineColor,
12        Style, StyleClassRef, StyleSelectors,
13    },
14};
15use bitflags::bitflags;
16use im::HashSet;
17use peniko::kurbo::{Affine, Point, Rect};
18use smallvec::SmallVec;
19use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc};
20use taffy::tree::NodeId;
21
22/// A stack of view attributes. Each entry is associated with a view decorator call.
23#[derive(Debug)]
24pub(crate) struct Stack<T> {
25    pub(crate) stack: SmallVec<[T; 1]>,
26}
27
28impl<T> Default for Stack<T> {
29    fn default() -> Self {
30        Stack {
31            stack: SmallVec::new(),
32        }
33    }
34}
35
36pub(crate) struct StackOffset<T> {
37    offset: usize,
38    phantom: PhantomData<T>,
39}
40
41impl<T> Clone for StackOffset<T> {
42    fn clone(&self) -> Self {
43        *self
44    }
45}
46
47impl<T> Copy for StackOffset<T> {}
48
49impl<T> Stack<T> {
50    pub fn next_offset(&mut self) -> StackOffset<T> {
51        StackOffset {
52            offset: self.stack.len(),
53            phantom: PhantomData,
54        }
55    }
56    pub fn push(&mut self, value: T) {
57        self.stack.push(value);
58    }
59    pub fn set(&mut self, offset: StackOffset<T>, value: T) {
60        self.stack[offset.offset] = value;
61    }
62
63    pub fn update(&mut self, offset: StackOffset<T>, update: impl Fn(&mut T) + 'static) {
64        update(&mut self.stack[offset.offset]);
65    }
66}
67
68prop_extractor! {
69    pub(crate) ViewStyleProps {
70        pub border_radius: BorderRadius,
71
72        pub outline: Outline,
73        pub outline_color: OutlineColor,
74        pub border_color: BorderColor,
75        pub background: Background,
76        pub shadow: BoxShadowProp,
77    }
78}
79
80bitflags! {
81    #[derive(Default, Copy, Clone, Debug)]
82    #[must_use]
83    pub(crate) struct ChangeFlags: u8 {
84        const STYLE = 1;
85        const LAYOUT = 1 << 1;
86    }
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq)]
90pub enum IsHiddenState {
91    Visible(taffy::style::Display),
92    AnimatingOut(taffy::style::Display),
93    Hidden,
94    None,
95}
96impl IsHiddenState {
97    pub(crate) fn get_display(&self) -> Option<taffy::style::Display> {
98        match self {
99            IsHiddenState::AnimatingOut(dis) => Some(*dis),
100            _ => None,
101        }
102    }
103
104    pub(crate) fn transition(
105        &mut self,
106        computed_display: taffy::Display,
107        remove_animations: impl FnOnce() -> bool,
108        add_animations: impl FnOnce(),
109        stop_reset_animations: impl FnOnce(),
110        num_waiting_anim: impl FnOnce() -> u16,
111    ) {
112        let computed_has_hide = computed_display == taffy::Display::None;
113        *self = match self {
114            // initial states (makes it so that the animations aren't run on initial app/view load)
115            Self::None if computed_has_hide => Self::Hidden,
116            Self::None if !computed_has_hide => Self::Visible(computed_display),
117            // do nothing
118            Self::Visible(dis) if !computed_has_hide => Self::Visible(*dis),
119            // transition to hidden
120            Self::Visible(dis) if computed_has_hide => {
121                let active_animations = remove_animations();
122                if active_animations {
123                    Self::AnimatingOut(*dis)
124                } else {
125                    Self::Hidden
126                }
127            }
128            Self::AnimatingOut(_) if !computed_has_hide => {
129                stop_reset_animations();
130                Self::Visible(computed_display)
131            }
132            Self::AnimatingOut(dis) if computed_has_hide => {
133                if num_waiting_anim() == 0 {
134                    Self::Hidden
135                } else {
136                    Self::AnimatingOut(*dis)
137                }
138            }
139            Self::Hidden if computed_has_hide => Self::Hidden,
140            Self::Hidden if !computed_has_hide => {
141                add_animations();
142                Self::Visible(computed_display)
143            }
144            _ => unreachable!(),
145        };
146    }
147}
148
149/// View state stores internal state associated with a view which is owned and managed by Floem.
150pub struct ViewState {
151    pub(crate) node: NodeId,
152    pub(crate) requested_changes: ChangeFlags,
153    pub(crate) style: Stack<Style>,
154    /// Layout is requested on all direct and indirect children.
155    pub(crate) request_style_recursive: bool,
156    pub(crate) has_style_selectors: StyleSelectors,
157    pub(crate) viewport: Option<Rect>,
158    pub(crate) layout_rect: Rect,
159    pub(crate) layout_props: LayoutProps,
160    pub(crate) view_style_props: ViewStyleProps,
161    pub(crate) animations: Stack<Animation>,
162    pub(crate) classes: Vec<StyleClassRef>,
163    pub(crate) dragging_style: Option<Style>,
164    pub(crate) combined_style: Style,
165    pub(crate) taffy_style: taffy::style::Style,
166    pub(crate) event_listeners: HashMap<EventListener, Vec<Rc<RefCell<EventCallback>>>>,
167    pub(crate) context_menu: Option<Rc<MenuCallback>>,
168    pub(crate) popout_menu: Option<Rc<MenuCallback>>,
169    pub(crate) resize_listener: Option<Rc<RefCell<ResizeListener>>>,
170    pub(crate) window_origin: Point,
171    pub(crate) move_listener: Option<Rc<RefCell<MoveListener>>>,
172    pub(crate) cleanup_listener: Option<Rc<dyn Fn()>>,
173    pub(crate) last_pointer_down: Option<PointerInputEvent>,
174    pub(crate) is_hidden_state: IsHiddenState,
175    pub(crate) num_waiting_animations: u16,
176    pub(crate) disable_default_events: HashSet<EventListener>,
177    pub(crate) pointer_events: bool,
178    pub(crate) transform: Affine,
179    pub(crate) debug_name: SmallVec<[String; 1]>,
180}
181
182impl ViewState {
183    pub(crate) fn new(taffy: &mut taffy::TaffyTree) -> Self {
184        Self {
185            node: taffy.new_leaf(taffy::style::Style::DEFAULT).unwrap(),
186            viewport: None,
187            style: Default::default(),
188            layout_rect: Rect::ZERO,
189            layout_props: Default::default(),
190            view_style_props: Default::default(),
191            requested_changes: ChangeFlags::all(),
192            request_style_recursive: false,
193            has_style_selectors: StyleSelectors::default(),
194            animations: Default::default(),
195            classes: Vec::new(),
196            combined_style: Style::new(),
197            taffy_style: taffy::style::Style::DEFAULT,
198            dragging_style: None,
199            event_listeners: HashMap::new(),
200            context_menu: None,
201            popout_menu: None,
202            resize_listener: None,
203            move_listener: None,
204            cleanup_listener: None,
205            last_pointer_down: None,
206            window_origin: Point::ZERO,
207            is_hidden_state: IsHiddenState::None,
208            num_waiting_animations: 0,
209            disable_default_events: HashSet::new(),
210            pointer_events: true,
211            transform: Affine::IDENTITY,
212            debug_name: Default::default(),
213        }
214    }
215
216    /// Returns `true` if a new frame is requested.
217    #[allow(clippy::too_many_arguments)]
218    pub(crate) fn compute_style(
219        &mut self,
220        view_style: Option<Style>,
221        interact_state: InteractionState,
222        screen_size_bp: ScreenSizeBp,
223        view_class: Option<StyleClassRef>,
224        context: &Style,
225    ) -> bool {
226        let mut new_frame = false;
227        let mut computed_style = Style::new();
228        if let Some(view_style) = view_style {
229            computed_style.apply_mut(view_style);
230        }
231        if let Some(view_class) = view_class {
232            computed_style = computed_style.apply_classes_from_context(&[view_class], context);
233        }
234        computed_style = computed_style
235            .apply_classes_from_context(&self.classes, context)
236            .apply(self.style());
237
238        self.has_style_selectors = computed_style.selectors();
239
240        computed_style.apply_interact_state(&interact_state, screen_size_bp);
241
242        for animation in self
243            .animations
244            .stack
245            .iter_mut()
246            .filter(|anim| anim.can_advance() || anim.should_apply_folded())
247        {
248            if animation.can_advance() {
249                new_frame = true;
250
251                animation.animate_into(&mut computed_style);
252
253                animation.advance();
254            } else {
255                animation.apply_folded(&mut computed_style)
256            }
257            debug_assert!(!animation.is_idle());
258        }
259
260        self.combined_style = computed_style;
261
262        new_frame
263    }
264
265    pub(crate) fn has_active_animation(&self) -> bool {
266        for animation in self.animations.stack.iter() {
267            if animation.is_in_progress() {
268                return true;
269            }
270        }
271        false
272    }
273
274    pub(crate) fn style(&self) -> Style {
275        let mut result = Style::new();
276        for entry in self.style.stack.iter() {
277            result.apply_mut(entry.clone());
278        }
279        result
280    }
281
282    pub(crate) fn add_event_listener(
283        &mut self,
284        listener: EventListener,
285        action: Box<EventCallback>,
286    ) {
287        self.event_listeners
288            .entry(listener)
289            .or_default()
290            .push(Rc::new(RefCell::new(action)));
291    }
292
293    pub(crate) fn update_resize_listener(&mut self, action: Box<ResizeCallback>) {
294        self.resize_listener = Some(Rc::new(RefCell::new(ResizeListener {
295            rect: Rect::ZERO,
296            callback: action,
297        })));
298    }
299
300    pub(crate) fn update_move_listener(&mut self, action: Box<dyn Fn(Point)>) {
301        self.move_listener = Some(Rc::new(RefCell::new(MoveListener {
302            window_origin: Point::ZERO,
303            callback: action,
304        })));
305    }
306
307    pub(crate) fn update_cleanup_listener(&mut self, action: impl Fn() + 'static) {
308        self.cleanup_listener = Some(Rc::new(action));
309    }
310}