1use crate::{
2 animate::Animation,
3 context::{
4 CleanupListeners, EventCallback, InteractionState, MenuCallback, MoveListeners,
5 ResizeCallback, ResizeListeners,
6 },
7 event::EventListener,
8 prop_extractor,
9 responsive::ScreenSizeBp,
10 style::{
11 Background, BorderColorProp, BorderRadiusProp, BoxShadowProp, LayoutProps, Outline,
12 OutlineColor, Style, StyleClassRef, StyleSelectors, resolve_nested_maps,
13 },
14};
15use bitflags::bitflags;
16use imbl::HashSet;
17use peniko::kurbo::{Affine, Point, Rect};
18use smallvec::SmallVec;
19use std::{cell::RefCell, collections::HashMap, marker::PhantomData, rc::Rc};
20use taffy::tree::NodeId;
21use ui_events::pointer::PointerState;
22
23#[derive(Debug)]
25pub struct Stack<T> {
26 pub stack: SmallVec<[T; 1]>,
27}
28
29impl<T> Default for Stack<T> {
30 fn default() -> Self {
31 Stack {
32 stack: SmallVec::new(),
33 }
34 }
35}
36
37pub struct StackOffset<T> {
38 offset: usize,
39 phantom: PhantomData<T>,
40}
41
42impl<T> Clone for StackOffset<T> {
43 fn clone(&self) -> Self {
44 *self
45 }
46}
47
48impl<T> Copy for StackOffset<T> {}
49
50impl<T> Stack<T> {
51 pub fn next_offset(&mut self) -> StackOffset<T> {
52 StackOffset {
53 offset: self.stack.len(),
54 phantom: PhantomData,
55 }
56 }
57 pub fn push(&mut self, value: T) {
58 self.stack.push(value);
59 }
60 pub fn set(&mut self, offset: StackOffset<T>, value: T) {
61 self.stack[offset.offset] = value;
62 }
63
64 pub fn update(&mut self, offset: StackOffset<T>, update: impl Fn(&mut T) + 'static) {
65 update(&mut self.stack[offset.offset]);
66 }
67
68 pub fn get(&mut self, offset: StackOffset<T>) -> &T {
69 &self.stack[offset.offset]
70 }
71}
72
73#[cfg(feature = "vello")]
74prop_extractor! {
75 pub(crate) ViewStyleProps {
76 pub border_radius: BorderRadiusProp,
77 pub border_progress: crate::style::BorderProgress,
78
79 pub outline: Outline,
80 pub outline_color: OutlineColor,
81 pub outline_progress: crate::style::OutlineProgress,
82 pub border_color: BorderColorProp,
83 pub background: Background,
84 pub shadow: BoxShadowProp,
85 }
86}
87#[cfg(not(feature = "vello"))]
89prop_extractor! {
90 pub(crate) ViewStyleProps {
91 pub border_radius: BorderRadiusProp,
92
93 pub outline: Outline,
94 pub outline_color: OutlineColor,
95 pub border_color: BorderColorProp,
96 pub background: Background,
97 pub shadow: BoxShadowProp,
98 }
99}
100
101bitflags! {
102 #[derive(Default, Copy, Clone, Debug)]
103 #[must_use]
104 pub(crate) struct ChangeFlags: u8 {
105 const STYLE = 1;
106 const LAYOUT = 1 << 1;
107 const VIEW_STYLE = 1 << 2;
108 }
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub enum IsHiddenState {
113 Visible(taffy::style::Display),
114 AnimatingOut(taffy::style::Display),
115 Hidden,
116 None,
117}
118impl IsHiddenState {
119 pub(crate) fn get_display(&self) -> Option<taffy::style::Display> {
120 match self {
121 IsHiddenState::AnimatingOut(dis) => Some(*dis),
122 _ => None,
123 }
124 }
125
126 pub(crate) fn transition(
127 &mut self,
128 computed_display: taffy::Display,
129 remove_animations: impl FnOnce() -> bool,
130 add_animations: impl FnOnce(),
131 stop_reset_animations: impl FnOnce(),
132 num_waiting_anim: impl FnOnce() -> u16,
133 ) {
134 let computed_has_hide = computed_display == taffy::Display::None;
135 *self = match self {
136 Self::None if computed_has_hide => Self::Hidden,
138 Self::None if !computed_has_hide => Self::Visible(computed_display),
139 Self::Visible(dis) if !computed_has_hide => Self::Visible(*dis),
141 Self::Visible(dis) if computed_has_hide => {
143 let active_animations = remove_animations();
144 if active_animations {
145 Self::AnimatingOut(*dis)
146 } else {
147 Self::Hidden
148 }
149 }
150 Self::AnimatingOut(_) if !computed_has_hide => {
151 stop_reset_animations();
152 Self::Visible(computed_display)
153 }
154 Self::AnimatingOut(dis) if computed_has_hide => {
155 if num_waiting_anim() == 0 {
156 Self::Hidden
157 } else {
158 Self::AnimatingOut(*dis)
159 }
160 }
161 Self::Hidden if computed_has_hide => Self::Hidden,
162 Self::Hidden if !computed_has_hide => {
163 add_animations();
164 Self::Visible(computed_display)
165 }
166 _ => unreachable!(),
167 };
168 }
169}
170
171pub struct ViewState {
173 pub(crate) node: NodeId,
174 pub(crate) requested_changes: ChangeFlags,
175 pub(crate) style: Stack<Style>,
176 pub(crate) view_style_offset: StackOffset<Style>,
179 pub(crate) request_style_recursive: bool,
181 pub(crate) has_style_selectors: StyleSelectors,
182 pub(crate) viewport: Option<Rect>,
183 pub(crate) layout_rect: Rect,
184 pub(crate) layout_props: LayoutProps,
185 pub(crate) view_style_props: ViewStyleProps,
186 pub(crate) animations: Stack<Animation>,
187 pub(crate) classes: Vec<StyleClassRef>,
188 pub(crate) dragging_style: Option<Style>,
189 pub(crate) combined_style: Style,
191 pub(crate) computed_style: Style,
193 pub(crate) taffy_style: taffy::style::Style,
194 pub(crate) event_listeners: HashMap<EventListener, Vec<Rc<RefCell<EventCallback>>>>,
195 pub(crate) context_menu: Option<Rc<MenuCallback>>,
196 pub(crate) popout_menu: Option<Rc<MenuCallback>>,
197 pub(crate) resize_listeners: Rc<RefCell<ResizeListeners>>,
198 pub(crate) window_origin: Point,
199 pub(crate) move_listeners: Rc<RefCell<MoveListeners>>,
200 pub(crate) cleanup_listeners: Rc<RefCell<CleanupListeners>>,
201 pub(crate) last_pointer_down: Option<PointerState>,
202 pub(crate) is_hidden_state: IsHiddenState,
203 pub(crate) num_waiting_animations: u16,
204 pub(crate) disable_default_events: HashSet<EventListener>,
205 pub(crate) transform: Affine,
206 pub(crate) debug_name: SmallVec<[String; 1]>,
207}
208
209impl ViewState {
210 pub(crate) fn new(taffy: &mut taffy::TaffyTree) -> Self {
211 let mut style = Stack::<Style>::default();
212 let view_style_offset = style.next_offset();
213 style.push(Style::new());
214
215 Self {
216 node: taffy.new_leaf(taffy::style::Style::DEFAULT).unwrap(),
217 viewport: None,
218 style,
219 view_style_offset,
220 layout_rect: Rect::ZERO,
221 layout_props: Default::default(),
222 view_style_props: Default::default(),
223 requested_changes: ChangeFlags::all(),
224 request_style_recursive: false,
225 has_style_selectors: StyleSelectors::default(),
226 animations: Default::default(),
227 classes: Vec::new(),
228 combined_style: Style::new(),
229 computed_style: Style::new(),
230 taffy_style: taffy::style::Style::DEFAULT,
231 dragging_style: None,
232 event_listeners: HashMap::new(),
233 context_menu: None,
234 popout_menu: None,
235 resize_listeners: Default::default(),
236 move_listeners: Default::default(),
237 cleanup_listeners: Default::default(),
238 last_pointer_down: None,
239 window_origin: Point::ZERO,
240 is_hidden_state: IsHiddenState::None,
241 num_waiting_animations: 0,
242 disable_default_events: HashSet::new(),
243 transform: Affine::IDENTITY,
244 debug_name: Default::default(),
245 }
246 }
247
248 pub(crate) fn compute_combined(
253 &mut self,
254 interact_state: InteractionState,
255 screen_size_bp: ScreenSizeBp,
256 view_class: Option<StyleClassRef>,
257 context: &Style,
258 cx_hidden: bool,
259 ) -> (bool, bool) {
260 let mut new_frame = false;
261
262 let mut combined_style = Style::new();
264
265 let mut classes: SmallVec<[_; 4]> = SmallVec::new();
266
267 if let Some(view_class) = view_class {
269 classes.insert(0, view_class);
270 }
271
272 for class in &self.classes {
273 classes.push(*class);
274 }
275
276 let mut new_context = context.clone();
277 let mut new_classes = false;
278
279 let (resolved_style, classes_applied) = resolve_nested_maps(
280 combined_style,
281 &interact_state,
282 screen_size_bp,
283 &classes,
284 &mut new_context,
285 );
286 combined_style = resolved_style;
287 new_classes |= classes_applied;
288
289 let self_style = self.style();
290
291 combined_style.apply_mut(self_style.clone());
292
293 let (resolved_style, classes_applied) = resolve_nested_maps(
294 combined_style,
295 &interact_state,
296 screen_size_bp,
297 &classes,
298 &mut new_context,
299 );
300 combined_style = resolved_style;
301 new_classes |= classes_applied;
302
303 self.has_style_selectors = combined_style.selectors();
305
306 for animation in self
308 .animations
309 .stack
310 .iter_mut()
311 .filter(|anim| anim.can_advance() || anim.should_apply_folded())
312 {
313 if animation.can_advance() {
314 new_frame = true;
315 animation.animate_into(&mut combined_style);
316 animation.advance();
317 } else {
318 animation.apply_folded(&mut combined_style)
319 }
320 debug_assert!(!animation.is_idle());
321 }
322
323 if cx_hidden {
325 combined_style = combined_style.hide();
326 }
327
328 self.combined_style = combined_style;
329 (new_frame, new_classes)
330 }
331
332 pub(crate) fn has_active_animation(&self) -> bool {
333 for animation in self.animations.stack.iter() {
334 if animation.is_in_progress() {
335 return true;
336 }
337 }
338 false
339 }
340
341 pub(crate) fn style(&self) -> Style {
342 let mut result = Style::new();
343 for entry in self.style.stack.iter() {
344 result.apply_mut(entry.clone());
345 }
346 result
347 }
348
349 pub(crate) fn add_event_listener(
350 &mut self,
351 listener: EventListener,
352 action: Box<EventCallback>,
353 ) {
354 self.event_listeners
355 .entry(listener)
356 .or_default()
357 .push(Rc::new(RefCell::new(action)));
358 }
359
360 pub(crate) fn add_resize_listener(&mut self, action: Rc<ResizeCallback>) {
361 self.resize_listeners.borrow_mut().callbacks.push(action);
362 }
363
364 pub(crate) fn add_move_listener(&mut self, action: Rc<dyn Fn(Point)>) {
365 self.move_listeners.borrow_mut().callbacks.push(action);
366 }
367
368 pub(crate) fn add_cleanup_listener(&mut self, action: Rc<dyn Fn()>) {
369 self.cleanup_listeners.borrow_mut().push(action);
370 }
371}