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
44                    .layout_style
45                    .as_ref()
46                    .map(|style| style.get_width())
47                    .unwrap_or_default(),
48                Direction::Vertical => c
49                    .layout_style
50                    .as_ref()
51                    .map(|style| style.get_height())
52                    .unwrap_or_default(),
53            })
54            .collect()
55    }
56
57    fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
58        let mut is_ready = false;
59        for component in self.components.iter_mut() {
60            if Pin::new(component).poll_change(cx).is_ready() {
61                is_ready = true;
62            }
63        }
64
65        if is_ready {
66            Poll::Ready(())
67        } else {
68            Poll::Pending
69        }
70    }
71}
72
73pub struct InstantiatedComponent {
74    key: ElementKey,
75    hooks: Vec<Box<dyn AnyHook>>,
76    component: Box<dyn AnyComponent>,
77    helper: Box<dyn ComponentHelperExt>,
78    children: Components,
79    first_update: bool,
80    layout_style: Option<LayoutStyle>,
81    has_transparent_layout: bool,
82}
83
84impl InstantiatedComponent {
85    pub fn new(key: ElementKey, mut props: AnyProps, helper: Box<dyn ComponentHelperExt>) -> Self {
86        let component = helper.new_component(props.borrow());
87        Self {
88            key,
89            hooks: Default::default(),
90            layout_style: None,
91            component,
92            children: Components::default(),
93            helper,
94            first_update: true,
95            has_transparent_layout: false,
96        }
97    }
98
99    pub fn component(&self) -> &dyn AnyComponent {
100        &*self.component
101    }
102
103    pub fn update(
104        &mut self,
105        terminal: &mut Terminal,
106        context_stack: &mut ContextStack,
107        mut props: AnyProps,
108    ) {
109        let mut updater = ComponentUpdater::new(
110            self.key.clone(),
111            context_stack,
112            terminal,
113            &mut self.children,
114            &mut self.layout_style,
115        );
116        self.hooks.pre_component_update(&mut updater);
117        self.helper.update_component(
118            &mut self.component,
119            props.borrow(),
120            Hooks::new(&mut self.hooks, self.first_update),
121            &mut updater,
122        );
123        self.hooks.post_component_update(&mut updater);
124        self.first_update = false;
125        self.has_transparent_layout = updater.has_transparent_layout();
126    }
127
128    pub fn draw(&mut self, drawer: &mut ComponentDrawer) {
129        let default_layout_style = LayoutStyle::default();
130        let layout_style = self.layout_style.as_ref().unwrap_or(&default_layout_style);
131
132        let area = if self.has_transparent_layout {
133            drawer.area
134        } else {
135            layout_style.inner_area(drawer.area)
136        };
137
138        drawer.area = area;
139
140        // 先渲染在计算子组件的areas
141        self.hooks.pre_component_draw(drawer);
142
143        // drawer.ares可能在组件绘制时改变
144        self.component.draw(drawer);
145
146        // 更新子组件的区域
147        self.component
148            .update_children_areas(&self.children, layout_style, drawer);
149
150        let children_areas = drawer.children_areas.clone();
151
152        for (child, area) in self
153            .children
154            .components
155            .iter_mut()
156            .zip(children_areas.iter())
157        {
158            drawer.area = *area;
159            child.draw(drawer);
160        }
161        self.hooks.post_component_draw(drawer);
162    }
163
164    pub(crate) fn poll_change(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> {
165        let component_status = Pin::new(&mut *self.component).poll_change(cx);
166        let children_status = Pin::new(&mut self.children).poll_change(cx);
167        let hooks_status = Pin::new(&mut self.hooks).poll_change(cx);
168        if component_status.is_ready() || children_status.is_ready() || hooks_status.is_ready() {
169            Poll::Ready(())
170        } else {
171            Poll::Pending
172        }
173    }
174
175    pub async fn wait(&mut self) {
176        let mut self_mut = Pin::new(self);
177        poll_fn(|cx| self_mut.as_mut().poll_change(cx)).await;
178    }
179}