Skip to main content

ferroui_base/layout/
layout_manager.rs

1use rustc_hash::FxHashMap;
2use super::layout_queue::LayoutQueue;
3use super::{
4    BringIntoViewRequest, EffectiveViewportChangedEventArgs, IBringIntoViewLayoutManager, ILayoutManager, ILayoutRoot,
5    Layoutable,
6};
7use crate::animation::TimeSpan;
8use crate::logging::{LogArea, LogEventLevel, Logger};
9use crate::media::MediaContext;
10use crate::rendering::LayoutPassTiming;
11use crate::threading::Dispatcher;
12use crate::utilities::HandlerList;
13use crate::{Matrix, Rect, Ref, Size, Visual};
14use std::any::Any;
15use std::cell::{Cell, RefCell};
16use std::rc::{Rc, Weak};
17use std::time::Duration;
18
19const MAX_PASSES: i32 = 10;
20
21struct EffectiveViewportChangedListener {
22    listener: Ref<Layoutable>,
23    viewport: Cell<Option<Rect>>,
24}
25
26#[derive(Clone, Copy, PartialEq, Eq)]
27enum ArrangeResult {
28    Arranged,
29    NotVisible,
30    AncestorMeasureInvalid,
31}
32
33/// Manages measuring and arranging of controls.
34pub struct LayoutManager {
35    this: Weak<LayoutManager>,
36    owner: Weak<dyn ILayoutRoot>,
37    to_measure: RefCell<LayoutQueue<Ref<Layoutable>>>,
38    to_arrange: RefCell<LayoutQueue<Ref<Layoutable>>>,
39    to_arrange_after_measure: RefCell<Vec<Ref<Layoutable>>>,
40    effective_viewport_changed_listeners: RefCell<Vec<Rc<EffectiveViewportChangedListener>>>,
41    /// Not upstream: the copy of the listeners and the viewports of their ancestors that
42    /// [`raise_effective_viewport_changed`](Self::raise_effective_viewport_changed) works on, kept
43    /// for the next layout pass (empty between passes).
44    effective_viewport_scratch: RefCell<(Vec<Rc<EffectiveViewportChangedListener>>, FxHashMap<usize, Rect>)>,
45    bring_into_view_requests: RefCell<Option<Vec<Rc<dyn BringIntoViewRequest>>>>,
46    disposed: Cell<bool>,
47    queued: Cell<bool>,
48    running: Cell<bool>,
49    processing_bring_into_view_requests: Cell<bool>,
50    total_pass_count: Cell<i32>,
51    layout_updated: HandlerList<dyn Fn()>,
52    layout_pass_timed: RefCell<Option<Rc<dyn Fn(LayoutPassTiming)>>>,
53}
54
55impl LayoutManager {
56    /// Creates a layout manager for a layout root.
57    pub fn new(owner: Weak<dyn ILayoutRoot>) -> Rc<Self> {
58        Rc::new_cyclic(|this| Self {
59            this: this.clone(),
60            owner,
61            to_measure: RefCell::new(LayoutQueue::new(|v| !v.is_measure_valid())),
62            to_arrange: RefCell::new(LayoutQueue::new(|v| !v.is_arrange_valid())),
63            to_arrange_after_measure: RefCell::new(Vec::new()),
64            effective_viewport_changed_listeners: RefCell::new(Vec::new()),
65            effective_viewport_scratch: RefCell::new((Vec::new(), FxHashMap::default())),
66            bring_into_view_requests: RefCell::new(None),
67            disposed: Cell::new(false),
68            queued: Cell::new(false),
69            running: Cell::new(false),
70            processing_bring_into_view_requests: Cell::new(false),
71            total_pass_count: Cell::new(0),
72            layout_updated: HandlerList::new(),
73            layout_pass_timed: RefCell::new(None),
74        })
75    }
76
77    /// Whether a layout pass is currently executing.
78    pub fn is_in_layout_pass(&self) -> bool {
79        self.running.get()
80    }
81
82    /// The number of layout passes executed so far.
83    pub fn total_pass_count(&self) -> i32 {
84        self.total_pass_count.get()
85    }
86
87    /// The callback that receives the timing of each layout pass (C#
88    /// `LayoutPassTimed`, internal), or `None`.
89    pub fn layout_pass_timed(&self) -> Option<Rc<dyn Fn(LayoutPassTiming)>> {
90        self.layout_pass_timed.borrow().clone()
91    }
92
93    /// Sets the callback that receives the timing of each layout pass.
94    pub fn set_layout_pass_timed(&self, value: Option<Rc<dyn Fn(LayoutPassTiming)>>) {
95        *self.layout_pass_timed.borrow_mut() = value;
96    }
97
98    /// The number of controls queued for measure. For tests.
99    #[cfg(any(test, feature = "testing"))]
100    pub fn to_measure_count(&self) -> usize {
101        self.to_measure.borrow().count()
102    }
103
104    /// The number of controls queued for arrange. For tests.
105    #[cfg(any(test, feature = "testing"))]
106    pub fn to_arrange_count(&self) -> usize {
107        self.to_arrange.borrow().count()
108    }
109
110    fn is_owned_root(&self, control: &Layoutable) -> bool {
111        match (control.get_layout_root(), self.owner.upgrade()) {
112            (Some(root), Some(owner)) => std::ptr::addr_eq(Rc::as_ptr(&root), Rc::as_ptr(&owner)),
113            _ => false,
114        }
115    }
116
117    fn is_root_visual(control: &Layoutable) -> bool {
118        control.get_layout_root().is_some_and(|root| {
119            let root_visual = root.root_visual();
120            std::ptr::eq(&*root_visual as *const Layoutable, control as *const Layoutable)
121        })
122    }
123
124    /// C# `Stopwatch.GetTimestamp()`: the clock of the dispatcher of the
125    /// thread, in milliseconds.
126    fn get_timestamp() -> i64 {
127        Dispatcher::current_dispatcher().now()
128    }
129
130    fn execute_queued_layout_pass(&self) {
131        if !self.queued.get() {
132            return;
133        }
134        self.execute_layout_pass();
135    }
136
137    /// Attempts to execute each pending bring-into-view request once.
138    ///
139    /// Returns true if at least one request was executed; false if there was
140    /// nothing to do or no request could make progress.
141    fn process_bring_into_view_requests(&self) -> bool {
142        if self.processing_bring_into_view_requests.get()
143            || !self.bring_into_view_requests.borrow().as_ref().is_some_and(|requests| !requests.is_empty())
144        {
145            return false;
146        }
147
148        self.processing_bring_into_view_requests.set(true);
149        let _guard = RunningGuard(&self.processing_bring_into_view_requests);
150
151        let is_at = |requests: &Option<Vec<Rc<dyn BringIntoViewRequest>>>, i: usize, request: &Rc<dyn BringIntoViewRequest>| {
152            requests
153                .as_ref()
154                .and_then(|requests| requests.get(i))
155                .is_some_and(|current| std::ptr::addr_eq(Rc::as_ptr(current), Rc::as_ptr(request)))
156        };
157
158        let mut executed_any = false;
159        let mut i = 0;
160
161        loop {
162            let request = match self.bring_into_view_requests.borrow().as_ref().and_then(|requests| requests.get(i)) {
163                Some(request) => request.clone(),
164                None => break,
165            };
166
167            // The target has been detached, abort.
168            if !self.is_owned_root(&request.target()) {
169                let mut requests = self.bring_into_view_requests.borrow_mut();
170                if is_at(&requests, i, &request) {
171                    if let Some(requests) = requests.as_mut() {
172                        requests.remove(i);
173                    }
174                }
175                continue;
176            }
177
178            let executed = request.try_execute();
179            executed_any |= executed;
180
181            // Executing the request may have replaced it with a new one for
182            // the same target.
183            let mut requests = self.bring_into_view_requests.borrow_mut();
184            if executed && is_at(&requests, i, &request) {
185                if let Some(requests) = requests.as_mut() {
186                    requests.remove(i);
187                }
188            } else {
189                i += 1;
190            }
191        }
192
193        executed_any
194    }
195
196    fn inner_layout_pass(&self) {
197        for _ in 0..MAX_PASSES {
198            self.execute_measure_pass();
199            self.execute_arrange_pass();
200
201            if self.to_measure.borrow().count() == 0 {
202                break;
203            }
204        }
205    }
206
207    fn execute_measure_pass(&self) {
208        while self.to_measure.borrow().count() > 0 {
209            let control = self.to_measure.borrow_mut().dequeue();
210
211            if !control.is_measure_valid() {
212                Self::measure(&control);
213            }
214            self.to_arrange.borrow_mut().enqueue(control);
215        }
216    }
217
218    fn execute_arrange_pass(&self) {
219        while self.to_arrange.borrow().count() > 0 {
220            let control = self.to_arrange.borrow_mut().dequeue();
221
222            if !control.is_arrange_valid() && Self::arrange(&control) == ArrangeResult::AncestorMeasureInvalid {
223                self.to_arrange_after_measure.borrow_mut().push(control);
224            }
225        }
226
227        let pending = std::mem::take(&mut *self.to_arrange_after_measure.borrow_mut());
228        for control in pending {
229            self.invalidate_arrange(&control);
230        }
231    }
232
233    fn measure(control: &Layoutable) -> bool {
234        if !control.is_visible() || !control.is_attached_to_visual_tree() {
235            return false;
236        }
237
238        // Controls closest to the visual root need to be arranged first. We
239        // don't try to store ordered invalidation lists, instead we traverse
240        // the tree upwards, measuring the controls closest to the root first.
241        // This has been shown by benchmarks to be the fastest and most
242        // memory-efficient algorithm.
243        if let Some(parent) = control.visual_parent() {
244            if let Some(parent) = parent.downcast_ref::<Layoutable>() {
245                if !Self::measure(parent) {
246                    return false;
247                }
248            }
249        }
250
251        // If the control being measured has a valid measure here then its
252        // measure was handled by an ancestor and can be ignored. The measure
253        // may have also caused the control to be removed.
254        if !control.is_measure_valid() {
255            if Self::is_root_visual(control) {
256                control.measure(Size::INFINITY);
257            } else if let Some(previous) = control.previous_measure() {
258                control.measure(previous);
259            }
260        }
261
262        true
263    }
264
265    fn arrange(control: &Layoutable) -> ArrangeResult {
266        if !control.is_visible() || !control.is_attached_to_visual_tree() {
267            return ArrangeResult::NotVisible;
268        }
269
270        if let Some(parent) = control.visual_parent() {
271            if let Some(parent) = parent.downcast_ref::<Layoutable>() {
272                let parent_result = Self::arrange(parent);
273                if parent_result != ArrangeResult::Arranged {
274                    return parent_result;
275                }
276            }
277        }
278
279        if !control.is_measure_valid() {
280            return ArrangeResult::AncestorMeasureInvalid;
281        }
282
283        if !control.is_arrange_valid() {
284            if Self::is_root_visual(control) {
285                control.arrange(Rect::from_size(control.desired_size()));
286            } else if let Some(previous) = control.previous_arrange() {
287                control.arrange(previous);
288            }
289        }
290
291        ArrangeResult::Arranged
292    }
293
294    fn queue_layout_pass(&self) {
295        if !self.queued.get() && !self.running.get() {
296            self.queued.set(true);
297            let this = self.this.clone();
298            MediaContext::instance().begin_invoke_on_render(Rc::new(move || {
299                if let Some(this) = this.upgrade() {
300                    this.execute_queued_layout_pass();
301                }
302            }));
303        }
304    }
305
306    fn raise_effective_viewport_changed(&self) -> bool {
307        let start_count = self.to_measure.borrow().count() + self.to_arrange.borrow().count();
308
309        // The viewports of the ancestors the listeners share are computed once in the pass while
310        // nothing they are computed from changes (a handler of the event may change it, so the
311        // version of the geometry is checked before each listener).
312        let (mut listeners, mut ancestors) = std::mem::take(&mut *self.effective_viewport_scratch.borrow_mut());
313        let mut version = crate::visual::geometry_version();
314
315        listeners.extend(self.effective_viewport_changed_listeners.borrow().iter().cloned());
316        for l in &listeners {
317            if !l.listener.is_attached_to_visual_tree() {
318                continue;
319            }
320            let current = crate::visual::geometry_version();
321            if current != version {
322                ancestors.clear();
323                version = current;
324            }
325            let viewport = Self::calculate_effective_viewport_with(&l.listener, &mut ancestors);
326            if Some(viewport) != l.viewport.get() {
327                l.listener.raise_effective_viewport_changed(&EffectiveViewportChangedEventArgs::new(viewport));
328                l.viewport.set(Some(viewport));
329            }
330        }
331        listeners.clear();
332        ancestors.clear();
333        *self.effective_viewport_scratch.borrow_mut() = (listeners, ancestors);
334
335        start_count != self.to_measure.borrow().count() + self.to_arrange.borrow().count()
336    }
337
338    /// `CalculateEffectiveViewport` with the viewports of the ancestors in `ancestors` (by
339    /// address; valid while the geometry of the visuals does not change): the steps of
340    /// `CalculateEffectiveViewportCore` from the root down to the control, with the prefix of the
341    /// walk of an ancestor shared by the listeners below it (DEVIATIONS.md, Layout).
342    fn calculate_effective_viewport_with(control: &Visual, ancestors: &mut FxHashMap<usize, Rect>) -> Rect {
343        let viewport = match control.visual_parent() {
344            Some(parent) => Self::ancestor_viewport(&parent, ancestors),
345            None => Rect::from_size(control.bounds().size()),
346        };
347        // The target: translated into its coordinate space, without its clip and transform.
348        viewport.translate((-control.bounds().position()).into())
349    }
350
351    /// The viewport in the coordinate space of `control` as an ancestor of the target: its clip,
352    /// its position and its render transform applied.
353    fn ancestor_viewport(control: &Visual, ancestors: &mut FxHashMap<usize, Rect>) -> Rect {
354        let key = control as *const Visual as usize;
355        if let Some(viewport) = ancestors.get(&key) {
356            return *viewport;
357        }
358        let mut viewport = match control.visual_parent() {
359            Some(parent) => Self::ancestor_viewport(&parent, ancestors),
360            None => Rect::from_size(control.bounds().size()),
361        };
362        if control.clip_to_bounds() {
363            viewport = control.bounds().intersect(viewport);
364        }
365        viewport = viewport.translate((-control.bounds().position()).into());
366        if control.has_render_transform() {
367            if let Some(transform) = control.render_transform() {
368                match transform.value().try_invert() {
369                    Some(inverted_matrix) => {
370                        let origin = control.render_transform_origin().to_pixels(control.bounds().size());
371                        let offset = Matrix::create_translation(origin.x, origin.y);
372                        viewport = viewport.transform_to_aabb(-offset * inverted_matrix * offset);
373                    }
374                    None => viewport = Rect::default(),
375                }
376            }
377        }
378        ancestors.insert(key, viewport);
379        viewport
380    }
381}
382
383impl ILayoutManager for LayoutManager {
384    fn add_layout_updated(&self, handler: Rc<dyn Fn()>) -> u64 {
385        self.layout_updated.add(handler)
386    }
387
388    fn remove_layout_updated(&self, token: u64) {
389        self.layout_updated.remove(token);
390    }
391
392    fn invalidate_measure(&self, control: &Layoutable) {
393        Dispatcher::ui_thread().verify_access();
394
395        if self.disposed.get() {
396            return;
397        }
398        if !control.is_attached_to_visual_tree() {
399            debug_assert!(
400                false,
401                "LayoutManager.InvalidateMeasure called on a control that is detached from the visual tree."
402            );
403            return;
404        }
405        assert!(self.is_owned_root(control), "Attempt to call InvalidateMeasure on wrong LayoutManager.");
406
407        self.to_measure.borrow_mut().enqueue(control.to_ref());
408        self.queue_layout_pass();
409    }
410
411    fn invalidate_arrange(&self, control: &Layoutable) {
412        Dispatcher::ui_thread().verify_access();
413
414        if self.disposed.get() {
415            return;
416        }
417        if !control.is_attached_to_visual_tree() {
418            debug_assert!(
419                false,
420                "LayoutManager.InvalidateArrange called on a control that is detached from the visual tree."
421            );
422            return;
423        }
424        assert!(self.is_owned_root(control), "Attempt to call InvalidateArrange on wrong LayoutManager.");
425
426        self.to_arrange.borrow_mut().enqueue(control.to_ref());
427        self.queue_layout_pass();
428    }
429
430    fn execute_layout_pass(&self) {
431        Dispatcher::ui_thread().verify_access();
432
433        if self.disposed.get() {
434            return;
435        }
436
437        if !self.running.get() {
438            const TIMING_LOG_LEVEL: LogEventLevel = LogEventLevel::Information;
439            let capture_timing =
440                self.layout_pass_timed.borrow().is_some() || Logger::is_enabled(TIMING_LOG_LEVEL, LogArea::LAYOUT);
441            let mut starting_timestamp = 0;
442
443            if capture_timing {
444                if let Some(logger) = Logger::try_get(TIMING_LOG_LEVEL, LogArea::LAYOUT) {
445                    logger.log_with_values(
446                        Some(self as &dyn Any),
447                        "Started layout pass. To measure: {Measure} To arrange: {Arrange}",
448                        &[&self.to_measure.borrow().count(), &self.to_arrange.borrow().count()],
449                    );
450                }
451
452                starting_timestamp = Self::get_timestamp();
453            }
454
455            self.to_measure.borrow_mut().begin_loop(MAX_PASSES);
456            self.to_arrange.borrow_mut().begin_loop(MAX_PASSES);
457
458            self.running.set(true);
459            {
460                let _guard = RunningGuard(&self.running);
461                self.total_pass_count.set(self.total_pass_count.get() + 1);
462
463                for _ in 0..MAX_PASSES {
464                    self.inner_layout_pass();
465                    if self.raise_effective_viewport_changed() {
466                        continue;
467                    }
468
469                    // The layout is now stable: bring-into-view requests can
470                    // execute against final bounds. Executing them typically
471                    // changes scroll offsets, which invalidates layout again.
472                    if !self.process_bring_into_view_requests()
473                        || (self.to_measure.borrow().count() == 0 && self.to_arrange.borrow().count() == 0)
474                    {
475                        break;
476                    }
477                }
478            }
479
480            self.to_measure.borrow_mut().end_loop();
481            self.to_arrange.borrow_mut().end_loop();
482
483            if capture_timing {
484                let elapsed =
485                    Duration::from_millis(Self::get_timestamp().saturating_sub(starting_timestamp).max(0) as u64);
486                let layout_pass_timed = self.layout_pass_timed.borrow().clone();
487                if let Some(layout_pass_timed) = layout_pass_timed {
488                    layout_pass_timed(LayoutPassTiming::new(self.total_pass_count.get(), elapsed));
489                }
490
491                if let Some(logger) = Logger::try_get(TIMING_LOG_LEVEL, LogArea::LAYOUT) {
492                    logger.log_with_values(
493                        Some(self as &dyn Any),
494                        "Layout pass finished in {Time}",
495                        &[&TimeSpan::from(elapsed)],
496                    );
497                }
498            }
499        } else if self.processing_bring_into_view_requests.get() {
500            // A layout pass forced while executing a bring-into-view request
501            // is part of the enclosing pass: run inner passes inline, and let
502            // the enclosing pass raise the layout updated event once all
503            // requests have been processed.
504            for _ in 0..MAX_PASSES {
505                self.inner_layout_pass();
506
507                if !self.raise_effective_viewport_changed() {
508                    break;
509                }
510            }
511
512            return;
513        }
514
515        self.queued.set(false);
516        if !self.layout_updated.is_empty() {
517            for (_, handler) in self.layout_updated.snapshot().iter() {
518                handler();
519            }
520        }
521    }
522
523    fn execute_initial_layout_pass(&self) {
524        if self.disposed.get() {
525            return;
526        }
527
528        let Some(owner) = self.owner.upgrade() else { return };
529        let root = owner.root_visual();
530
531        self.running.set(true);
532        {
533            let _guard = RunningGuard(&self.running);
534            Self::measure(&root);
535            Self::arrange(&root);
536        }
537
538        // Running the initial layout pass may have caused some control to be
539        // invalidated so run a full layout pass now (this is usually due to
540        // scrollbars: it's not known whether they will need to be shown until
541        // the layout pass has run and if the first guess was incorrect the
542        // layout will need to be updated).
543        self.execute_layout_pass();
544    }
545
546    fn register_effective_viewport_listener(&self, control: &Layoutable) {
547        self.effective_viewport_changed_listeners
548            .borrow_mut()
549            .push(Rc::new(EffectiveViewportChangedListener { listener: control.to_ref(), viewport: Cell::new(None) }));
550    }
551
552    fn unregister_effective_viewport_listener(&self, control: &Layoutable) {
553        self.effective_viewport_changed_listeners
554            .borrow_mut()
555            .retain(|l| !std::ptr::eq(&*l.listener as *const Layoutable, control as *const Layoutable));
556    }
557
558    fn dispose(&self) {
559        self.disposed.set(true);
560        self.to_measure.borrow_mut().dispose();
561        self.to_arrange.borrow_mut().dispose();
562        *self.bring_into_view_requests.borrow_mut() = None;
563    }
564
565    fn as_bring_into_view_layout_manager(&self) -> Option<&dyn IBringIntoViewLayoutManager> {
566        Some(self)
567    }
568}
569
570impl IBringIntoViewLayoutManager for LayoutManager {
571    fn is_in_layout_pass(&self) -> bool {
572        LayoutManager::is_in_layout_pass(self)
573    }
574
575    fn enqueue_bring_into_view(&self, request: Rc<dyn BringIntoViewRequest>) {
576        Dispatcher::ui_thread().verify_access();
577
578        if self.disposed.get() {
579            return;
580        }
581
582        // The targets are read outside of the borrow of the queue.
583        let target = request.target();
584        let pending: Vec<_> = self.bring_into_view_requests.borrow().clone().unwrap_or_default();
585        let existing = pending.iter().position(|existing| existing.target() == target);
586
587        {
588            let mut requests = self.bring_into_view_requests.borrow_mut();
589            let requests = requests.get_or_insert_with(Vec::new);
590
591            match existing.and_then(|i| requests.get_mut(i)) {
592                Some(existing) => *existing = request,
593                None => requests.push(request),
594            }
595        }
596
597        // The request will usually be consumed by the already pending layout
598        // pass that will lay out its target, but make sure a pass is scheduled
599        // in case there is none.
600        self.queue_layout_pass();
601    }
602}
603
604struct RunningGuard<'a>(&'a Cell<bool>);
605
606impl Drop for RunningGuard<'_> {
607    fn drop(&mut self) {
608        self.0.set(false);
609    }
610}