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