Skip to main content

i_slint_core/items/
component_container.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4/*!
5This module contains the builtin `ComponentContainer` and related items
6
7When adding an item or a property, it needs to be kept in sync with different place.
8Lookup the [`crate::items`] module documentation.
9*/
10use super::{Item, ItemConsts, ItemRc, RenderingResult};
11use crate::Coord;
12use crate::component_factory::{ComponentFactory, FactoryContext};
13use crate::input::{
14    FocusEvent, FocusEventResult, InputEventFilterResult, InputEventResult, InternalKeyEvent,
15    KeyEventResult, MouseEvent,
16};
17use crate::item_rendering::{CachedRenderingData, RenderRectangle};
18use crate::item_tree::{IndexRange, ItemTreeRc, ItemTreeWeak, ItemWeak};
19use crate::item_tree::{ItemTreeNode, ItemVisitorVTable, TraversalOrder, VisitChildrenResult};
20use crate::layout::{LayoutInfo, Orientation};
21use crate::lengths::{LogicalLength, LogicalRect, LogicalSize};
22use crate::properties::{Property, PropertyTracker};
23#[cfg(feature = "rtti")]
24use crate::rtti::*;
25use crate::window::WindowAdapter;
26use alloc::boxed::Box;
27use alloc::rc::Rc;
28use const_field_offset::FieldOffsets;
29use core::cell::RefCell;
30use core::pin::Pin;
31use i_slint_core_macros::*;
32use once_cell::unsync::OnceCell;
33
34#[repr(C)]
35#[derive(FieldOffsets, Default, SlintElement)]
36#[pin]
37/// The implementation of the `ComponentContainer` element
38pub struct ComponentContainer {
39    pub width: Property<LogicalLength>,
40    pub height: Property<LogicalLength>,
41    pub component_factory: Property<ComponentFactory>,
42    pub has_component: Property<bool>,
43
44    pub cached_rendering_data: CachedRenderingData,
45
46    component_tracker: OnceCell<Pin<Box<PropertyTracker>>>,
47    item_tree: RefCell<Option<ItemTreeRc>>,
48
49    my_component: OnceCell<ItemTreeWeak>,
50    embedding_item_tree_index: OnceCell<u32>,
51    self_weak: OnceCell<ItemWeak>,
52}
53
54impl ComponentContainer {
55    pub fn ensure_updated(self: Pin<&Self>) -> bool {
56        let factory = self
57            .component_tracker
58            .get()
59            .unwrap()
60            .as_ref()
61            .evaluate_if_dirty(|| self.component_factory());
62
63        let Some(factory) = factory else {
64            // Factory unchanged — still recurse into the embedded component
65            // so its repeaters and conditionals get instantiated.
66            if let Some(inner) = self.subtree_component().upgrade() {
67                return crate::item_tree::ensure_item_tree_instantiated(&inner);
68            }
69            return false;
70        };
71
72        let mut window = None;
73        if let Some(parent) = self.my_component.get().and_then(|x| x.upgrade()) {
74            vtable::VRc::borrow_pin(&parent).as_ref().window_adapter(false, &mut window);
75        }
76        let prevent_focus_change =
77            window.as_ref().is_some_and(|w| w.window().0.prevent_focus_change.replace(true));
78
79        let factory_context = FactoryContext {
80            parent_item_tree: self.my_component.get().unwrap().clone(),
81            parent_item_tree_index: *self.embedding_item_tree_index.get().unwrap(),
82        };
83
84        let product = factory.build(factory_context);
85
86        if let Some(w) = window {
87            w.window().0.prevent_focus_change.set(prevent_focus_change);
88        }
89
90        if let Some(item_tree) = product.clone() {
91            let item_tree = vtable::VRc::borrow_pin(&item_tree);
92            let root_item = item_tree.as_ref().get_item_ref(0);
93            if let Some(window_item) =
94                crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
95            {
96                // Do _not_ use a two-way binding: That causes evaluations of width and height to
97                // assert on recursive property evaluation.
98                let weak = self.self_weak.get().unwrap().clone();
99                window_item.width.set_binding(Box::new(move || {
100                    if let Some(self_rc) = weak.upgrade() {
101                        let self_pin = self_rc.borrow();
102                        if let Some(self_cc) = crate::items::ItemRef::downcast_pin::<Self>(self_pin)
103                        {
104                            return self_cc.width();
105                        }
106                    }
107                    Default::default()
108                }));
109                let weak = self.self_weak.get().unwrap().clone();
110                window_item.height.set_binding(Box::new(move || {
111                    if let Some(self_rc) = weak.upgrade() {
112                        let self_pin = self_rc.borrow();
113                        if let Some(self_cc) = crate::items::ItemRef::downcast_pin::<Self>(self_pin)
114                        {
115                            return self_cc.height();
116                        }
117                    }
118                    Default::default()
119                }));
120            }
121        }
122
123        self.has_component.set(product.is_some());
124
125        self.item_tree.replace(product);
126
127        if let Some(inner) = self.subtree_component().upgrade() {
128            crate::item_tree::ensure_item_tree_instantiated(&inner);
129        }
130        true
131    }
132
133    pub fn subtree_range(self: Pin<&Self>) -> IndexRange {
134        IndexRange { start: 0, end: if self.item_tree.borrow().is_some() { 1 } else { 0 } }
135    }
136
137    pub fn subtree_component(self: Pin<&Self>) -> ItemTreeWeak {
138        self.item_tree.borrow().as_ref().map_or(ItemTreeWeak::default(), vtable::VRc::downgrade)
139    }
140
141    pub fn visit_children_item(
142        self: Pin<&Self>,
143        _index: isize,
144        order: TraversalOrder,
145        visitor: vtable::VRefMut<ItemVisitorVTable>,
146    ) -> VisitChildrenResult {
147        let rc = self.item_tree.borrow().clone();
148        if let Some(rc) = &rc {
149            vtable::VRc::borrow_pin(rc).as_ref().visit_children_item(-1, order, visitor)
150        } else {
151            VisitChildrenResult::CONTINUE
152        }
153    }
154}
155
156impl Item for ComponentContainer {
157    fn init(self: Pin<&Self>, self_rc: &ItemRc) {
158        let rc = self_rc.item_tree();
159
160        self.my_component.set(vtable::VRc::downgrade(rc)).ok().unwrap();
161
162        // Find my embedding item_tree_index:
163        let pin_rc = vtable::VRc::borrow_pin(rc);
164        let item_tree = pin_rc.as_ref().get_item_tree();
165        let ItemTreeNode::Item { children_index, children_count, .. } =
166            item_tree[self_rc.index() as usize]
167        else {
168            panic!("ComponentContainer not found in item tree");
169        };
170
171        assert_eq!(children_count, 1);
172        assert!(matches!(item_tree[children_index as usize], ItemTreeNode::DynamicTree { .. }));
173
174        self.embedding_item_tree_index.set(children_index).ok().unwrap();
175
176        self.component_tracker.set(Box::pin(PropertyTracker::default())).ok().unwrap();
177        self.self_weak.set(self_rc.downgrade()).ok().unwrap();
178    }
179
180    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
181
182    fn layout_info(
183        self: Pin<&Self>,
184        orientation: Orientation,
185        _cross_axis_constraint: Coord,
186        _window_adapter: &Rc<dyn WindowAdapter>,
187        _self_rc: &ItemRc,
188    ) -> LayoutInfo {
189        self.ensure_updated();
190        if let Some(rc) = self.item_tree.borrow().clone() {
191            vtable::VRc::borrow_pin(&rc).as_ref().layout_info(orientation)
192        } else {
193            Default::default()
194        }
195    }
196
197    fn input_event_filter_before_children(
198        self: Pin<&Self>,
199        _: &MouseEvent,
200        _window_adapter: &Rc<dyn WindowAdapter>,
201        _self_rc: &ItemRc,
202        _: &mut super::MouseCursor,
203    ) -> InputEventFilterResult {
204        InputEventFilterResult::ForwardAndIgnore
205    }
206
207    fn input_event(
208        self: Pin<&Self>,
209        _: &MouseEvent,
210        _window_adapter: &Rc<dyn WindowAdapter>,
211        _self_rc: &ItemRc,
212        _: &mut super::MouseCursor,
213    ) -> InputEventResult {
214        InputEventResult::EventIgnored
215    }
216
217    fn focus_event(
218        self: Pin<&Self>,
219        _: &FocusEvent,
220        _window_adapter: &Rc<dyn WindowAdapter>,
221        _self_rc: &ItemRc,
222    ) -> FocusEventResult {
223        FocusEventResult::FocusIgnored
224    }
225
226    fn capture_key_event(
227        self: Pin<&Self>,
228        _: &InternalKeyEvent,
229        _window_adapter: &Rc<dyn WindowAdapter>,
230        _self_rc: &ItemRc,
231    ) -> KeyEventResult {
232        KeyEventResult::EventIgnored
233    }
234
235    fn key_event(
236        self: Pin<&Self>,
237        _: &InternalKeyEvent,
238        _window_adapter: &Rc<dyn WindowAdapter>,
239        _self_rc: &ItemRc,
240    ) -> KeyEventResult {
241        KeyEventResult::EventIgnored
242    }
243
244    fn render(
245        self: Pin<&Self>,
246        backend: &mut super::ItemRendererRef,
247        item_rc: &ItemRc,
248        size: LogicalSize,
249    ) -> RenderingResult {
250        backend.draw_rectangle(self, item_rc, size, &self.cached_rendering_data);
251        RenderingResult::ContinueRenderingChildren
252    }
253
254    fn bounding_rect(
255        self: core::pin::Pin<&Self>,
256        _window_adapter: &Rc<dyn WindowAdapter>,
257        _self_rc: &ItemRc,
258        geometry: LogicalRect,
259    ) -> LogicalRect {
260        geometry
261    }
262
263    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
264        false
265    }
266}
267
268impl RenderRectangle for ComponentContainer {
269    fn background(self: Pin<&Self>) -> crate::Brush {
270        self.item_tree
271            .borrow()
272            .clone()
273            .and_then(|item_tree| {
274                let item_tree = vtable::VRc::borrow_pin(&item_tree);
275                let root_item = item_tree.as_ref().get_item_ref(0);
276                crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
277                    .map(|window_item| window_item.background())
278            })
279            .unwrap_or_default()
280    }
281}
282
283impl ItemConsts for ComponentContainer {
284    const cached_rendering_data_offset: const_field_offset::FieldOffset<
285        ComponentContainer,
286        CachedRenderingData,
287    > = ComponentContainer::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
288}