ratatui_kit/component/
instantiated_component.rs

1use super::{AnyComponent, ComponentHelperExt};
2use crate::{
3    context::ContextStack,
4    element::ElementKey,
5    hooks::{AnyHook, Hook, Hooks},
6    multimap::RemoveOnlyMultimap,
7    props::AnyProps,
8    render::{ComponentDrawer, ComponentUpdater, layout_style::LayoutStyle},
9    terminal::Terminal,
10};
11use ratatui::layout::{Constraint, Direction};
12use std::{
13    future::poll_fn,
14    ops::{Deref, DerefMut},
15    pin::Pin,
16    task::{Context, Poll},
17};
18
19#[derive(Default)]
20pub struct Components {
21    pub components: RemoveOnlyMultimap<ElementKey, InstantiatedComponent>,
22}
23
24impl Deref for Components {
25    type Target = RemoveOnlyMultimap<ElementKey, InstantiatedComponent>;
26
27    fn deref(&self) -> &Self::Target {
28        &self.components
29    }
30}
31
32impl DerefMut for Components {
33    fn deref_mut(&mut self) -> &mut Self::Target {
34        &mut self.components
35    }
36}
37
38impl Components {
39    pub fn get_constraints(&self, direction: Direction) -> Vec<Constraint> {
40        self.components
41            .iter()
42            .map(|c| match direction {
43                Direction::Horizontal => c.layout_style.get_width(),
44                Direction::Vertical => c.layout_style.get_height(),
45            })
46            .collect()
47    }
48
49    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
50        let mut is_ready = false;
51        for component in self.components.iter_mut() {
52            if Pin::new(component).poll_change(cx).is_ready() {
53                is_ready = true;
54            }
55        }
56
57        if is_ready {
58            Poll::Ready(())
59        } else {
60            Poll::Pending
61        }
62    }
63}
64
65pub struct InstantiatedComponent {
66    key: ElementKey,
67    hooks: Vec<Box<dyn AnyHook>>,
68    component: Box<dyn AnyComponent>,
69    helper: Box<dyn ComponentHelperExt>,
70    children: Components,
71    first_update: bool,
72    layout_style: LayoutStyle,
73    has_transparent_layout: bool,
74}
75
76impl InstantiatedComponent {
77    pub fn new(key: ElementKey, mut props: AnyProps, helper: Box<dyn ComponentHelperExt>) -> Self {
78        let component = helper.new_component(props.borrow());
79        Self {
80            key,
81            hooks: Default::default(),
82            layout_style: LayoutStyle::default(),
83            component,
84            children: Components::default(),
85            helper,
86            first_update: true,
87            has_transparent_layout: false,
88        }
89    }
90
91    pub fn component(&self) -> &dyn AnyComponent {
92        &*self.component
93    }
94
95    pub fn update(
96        &mut self,
97        terminal: &mut Terminal,
98        context_stack: &mut ContextStack,
99        mut props: AnyProps,
100    ) {
101        let mut updater = ComponentUpdater::new(
102            self.key.clone(),
103            context_stack,
104            terminal,
105            &mut self.children,
106            &mut self.layout_style,
107        );
108        self.hooks.pre_component_update(&mut updater);
109        self.helper.update_component(
110            &mut self.component,
111            props.borrow(),
112            Hooks::new(&mut self.hooks, self.first_update),
113            &mut updater,
114        );
115        self.hooks.post_component_update(&mut updater);
116        self.first_update = false;
117        self.has_transparent_layout = updater.has_transparent_layout();
118
119        // 如果是透明布局,则继承第一个子组件的布局属性
120        if let Some(child) = self.children.iter().next()
121            && self.has_transparent_layout
122        {
123            self.layout_style = child.layout_style.clone();
124        }
125    }
126
127    pub fn draw(&mut self, drawer: &mut ComponentDrawer) {
128        let layout_style = &self.layout_style;
129
130        let area = if self.has_transparent_layout {
131            drawer.area
132        } else {
133            layout_style.inner_area(drawer.area)
134        };
135
136        drawer.area = area;
137
138        // 先渲染在计算子组件的areas
139        self.hooks.pre_component_draw(drawer);
140
141        // drawer.ares可能在组件绘制时改变
142        self.component.draw(drawer);
143
144        // 计算子组件的区域
145        let children_areas =
146            self.component
147                .calc_children_areas(&self.children, layout_style, drawer);
148
149        for (child, area) in self
150            .children
151            .components
152            .iter_mut()
153            .zip(children_areas.iter())
154        {
155            drawer.area = *area;
156            child.draw(drawer);
157        }
158        self.hooks.post_component_draw(drawer);
159    }
160
161    pub(crate) fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
162        let component_status = Pin::new(&mut *self.component).poll_change(cx);
163        let children_status = Pin::new(&mut self.children).poll_change(cx);
164        let hooks_status = Pin::new(&mut self.hooks).poll_change(cx);
165        if component_status.is_ready() || children_status.is_ready() || hooks_status.is_ready() {
166            Poll::Ready(())
167        } else {
168            Poll::Pending
169        }
170    }
171
172    pub async fn wait(&mut self) {
173        let mut self_mut = Pin::new(self);
174        poll_fn(|cx| self_mut.as_mut().poll_change(cx)).await;
175    }
176}
177
178impl Drop for InstantiatedComponent {
179    fn drop(&mut self) {
180        self.hooks.on_drop();
181    }
182}