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>) {
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            return;
65        };
66
67        let mut window = None;
68        if let Some(parent) = self.my_component.get().and_then(|x| x.upgrade()) {
69            vtable::VRc::borrow_pin(&parent).as_ref().window_adapter(false, &mut window);
70        }
71        let prevent_focus_change =
72            window.as_ref().is_some_and(|w| w.window().0.prevent_focus_change.replace(true));
73
74        let factory_context = FactoryContext {
75            parent_item_tree: self.my_component.get().unwrap().clone(),
76            parent_item_tree_index: *self.embedding_item_tree_index.get().unwrap(),
77        };
78
79        let product = factory.build(factory_context);
80
81        if let Some(w) = window {
82            w.window().0.prevent_focus_change.set(prevent_focus_change);
83        }
84
85        if let Some(item_tree) = product.clone() {
86            let item_tree = vtable::VRc::borrow_pin(&item_tree);
87            let root_item = item_tree.as_ref().get_item_ref(0);
88            if let Some(window_item) =
89                crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
90            {
91                // Do _not_ use a two-way binding: That causes evaluations of width and height to
92                // assert on recursive property evaluation.
93                let weak = self.self_weak.get().unwrap().clone();
94                window_item.width.set_binding(Box::new(move || {
95                    if let Some(self_rc) = weak.upgrade() {
96                        let self_pin = self_rc.borrow();
97                        if let Some(self_cc) = crate::items::ItemRef::downcast_pin::<Self>(self_pin)
98                        {
99                            return self_cc.width();
100                        }
101                    }
102                    Default::default()
103                }));
104                let weak = self.self_weak.get().unwrap().clone();
105                window_item.height.set_binding(Box::new(move || {
106                    if let Some(self_rc) = weak.upgrade() {
107                        let self_pin = self_rc.borrow();
108                        if let Some(self_cc) = crate::items::ItemRef::downcast_pin::<Self>(self_pin)
109                        {
110                            return self_cc.height();
111                        }
112                    }
113                    Default::default()
114                }));
115            }
116        }
117
118        self.has_component.set(product.is_some());
119
120        self.item_tree.replace(product);
121    }
122
123    pub fn subtree_range(self: Pin<&Self>) -> IndexRange {
124        IndexRange { start: 0, end: if self.item_tree.borrow().is_some() { 1 } else { 0 } }
125    }
126
127    pub fn subtree_component(self: Pin<&Self>) -> ItemTreeWeak {
128        self.item_tree.borrow().as_ref().map_or(ItemTreeWeak::default(), vtable::VRc::downgrade)
129    }
130
131    pub fn visit_children_item(
132        self: Pin<&Self>,
133        _index: isize,
134        order: TraversalOrder,
135        visitor: vtable::VRefMut<ItemVisitorVTable>,
136    ) -> VisitChildrenResult {
137        let rc = self.item_tree.borrow().clone();
138        if let Some(rc) = &rc {
139            vtable::VRc::borrow_pin(rc).as_ref().visit_children_item(-1, order, visitor)
140        } else {
141            VisitChildrenResult::CONTINUE
142        }
143    }
144}
145
146impl Item for ComponentContainer {
147    fn init(self: Pin<&Self>, self_rc: &ItemRc) {
148        let rc = self_rc.item_tree();
149
150        self.my_component.set(vtable::VRc::downgrade(rc)).ok().unwrap();
151
152        // Find my embedding item_tree_index:
153        let pin_rc = vtable::VRc::borrow_pin(rc);
154        let item_tree = pin_rc.as_ref().get_item_tree();
155        let ItemTreeNode::Item { children_index, children_count, .. } =
156            item_tree[self_rc.index() as usize]
157        else {
158            panic!("ComponentContainer not found in item tree");
159        };
160
161        assert_eq!(children_count, 1);
162        assert!(matches!(item_tree[children_index as usize], ItemTreeNode::DynamicTree { .. }));
163
164        self.embedding_item_tree_index.set(children_index).ok().unwrap();
165
166        self.component_tracker.set(Box::pin(PropertyTracker::default())).ok().unwrap();
167        self.self_weak.set(self_rc.downgrade()).ok().unwrap();
168    }
169
170    fn deinit(self: Pin<&Self>, _window_adapter: &Rc<dyn WindowAdapter>) {}
171
172    fn layout_info(
173        self: Pin<&Self>,
174        orientation: Orientation,
175        _cross_axis_constraint: Coord,
176        _window_adapter: &Rc<dyn WindowAdapter>,
177        _self_rc: &ItemRc,
178    ) -> LayoutInfo {
179        self.ensure_updated();
180        if let Some(rc) = self.item_tree.borrow().clone() {
181            vtable::VRc::borrow_pin(&rc).as_ref().layout_info(orientation)
182        } else {
183            Default::default()
184        }
185    }
186
187    fn input_event_filter_before_children(
188        self: Pin<&Self>,
189        _: &MouseEvent,
190        _window_adapter: &Rc<dyn WindowAdapter>,
191        _self_rc: &ItemRc,
192        _: &mut super::MouseCursor,
193    ) -> InputEventFilterResult {
194        InputEventFilterResult::ForwardAndIgnore
195    }
196
197    fn input_event(
198        self: Pin<&Self>,
199        _: &MouseEvent,
200        _window_adapter: &Rc<dyn WindowAdapter>,
201        _self_rc: &ItemRc,
202        _: &mut super::MouseCursor,
203    ) -> InputEventResult {
204        InputEventResult::EventIgnored
205    }
206
207    fn focus_event(
208        self: Pin<&Self>,
209        _: &FocusEvent,
210        _window_adapter: &Rc<dyn WindowAdapter>,
211        _self_rc: &ItemRc,
212    ) -> FocusEventResult {
213        FocusEventResult::FocusIgnored
214    }
215
216    fn capture_key_event(
217        self: Pin<&Self>,
218        _: &InternalKeyEvent,
219        _window_adapter: &Rc<dyn WindowAdapter>,
220        _self_rc: &ItemRc,
221    ) -> KeyEventResult {
222        KeyEventResult::EventIgnored
223    }
224
225    fn key_event(
226        self: Pin<&Self>,
227        _: &InternalKeyEvent,
228        _window_adapter: &Rc<dyn WindowAdapter>,
229        _self_rc: &ItemRc,
230    ) -> KeyEventResult {
231        KeyEventResult::EventIgnored
232    }
233
234    fn render(
235        self: Pin<&Self>,
236        backend: &mut super::ItemRendererRef,
237        item_rc: &ItemRc,
238        size: LogicalSize,
239    ) -> RenderingResult {
240        backend.draw_rectangle(self, item_rc, size, &self.cached_rendering_data);
241        RenderingResult::ContinueRenderingChildren
242    }
243
244    fn bounding_rect(
245        self: core::pin::Pin<&Self>,
246        _window_adapter: &Rc<dyn WindowAdapter>,
247        _self_rc: &ItemRc,
248        geometry: LogicalRect,
249    ) -> LogicalRect {
250        geometry
251    }
252
253    fn clips_children(self: core::pin::Pin<&Self>) -> bool {
254        false
255    }
256}
257
258impl RenderRectangle for ComponentContainer {
259    fn background(self: Pin<&Self>) -> crate::Brush {
260        self.item_tree
261            .borrow()
262            .clone()
263            .and_then(|item_tree| {
264                let item_tree = vtable::VRc::borrow_pin(&item_tree);
265                let root_item = item_tree.as_ref().get_item_ref(0);
266                crate::items::ItemRef::downcast_pin::<crate::items::WindowItem>(root_item)
267                    .map(|window_item| window_item.background())
268            })
269            .unwrap_or_default()
270    }
271}
272
273impl ItemConsts for ComponentContainer {
274    const cached_rendering_data_offset: const_field_offset::FieldOffset<
275        ComponentContainer,
276        CachedRenderingData,
277    > = ComponentContainer::FIELD_OFFSETS.cached_rendering_data().as_unpinned_projection();
278}