Skip to main content

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::UpdaterTerminal,
10};
11use ratatui::layout::{Constraint, Direction};
12use std::{
13    future::poll_fn,
14    ops::{Deref, DerefMut},
15    task::{Context, Poll},
16};
17
18#[derive(Default)]
19pub struct Components {
20    pub components: RemoveOnlyMultimap<ElementKey, InstantiatedComponent>,
21}
22
23impl Deref for Components {
24    type Target = RemoveOnlyMultimap<ElementKey, InstantiatedComponent>;
25
26    fn deref(&self) -> &Self::Target {
27        &self.components
28    }
29}
30
31impl DerefMut for Components {
32    fn deref_mut(&mut self) -> &mut Self::Target {
33        &mut self.components
34    }
35}
36
37impl Components {
38    pub fn get_constraints(&self, direction: Direction) -> Vec<Constraint> {
39        self.components
40            .iter()
41            .map(|c| match direction {
42                Direction::Horizontal => c.layout_style.get_width(),
43                Direction::Vertical => c.layout_style.get_height(),
44            })
45            .collect()
46    }
47
48    fn poll_change(&mut self, cx: &mut Context) -> Poll<()> {
49        let mut is_ready = false;
50        for component in self.components.iter_mut() {
51            if component.poll_change(cx).is_ready() {
52                is_ready = true;
53            }
54        }
55
56        if is_ready {
57            Poll::Ready(())
58        } else {
59            Poll::Pending
60        }
61    }
62}
63
64pub struct InstantiatedComponent {
65    key: ElementKey,
66    hooks: Vec<Box<dyn AnyHook>>,
67    component: Box<dyn AnyComponent>,
68    helper: Box<dyn ComponentHelperExt>,
69    children: Components,
70    first_update: bool,
71    layout_style: LayoutStyle,
72    has_transparent_layout: bool,
73}
74
75impl InstantiatedComponent {
76    pub fn new(key: ElementKey, mut props: AnyProps, helper: Box<dyn ComponentHelperExt>) -> Self {
77        let component = helper.new_component(props.borrow());
78        Self {
79            key,
80            hooks: Default::default(),
81            layout_style: LayoutStyle::default(),
82            component,
83            children: Components::default(),
84            helper,
85            first_update: true,
86            has_transparent_layout: false,
87        }
88    }
89
90    pub fn component(&self) -> &dyn AnyComponent {
91        &*self.component
92    }
93
94    pub fn update(
95        &mut self,
96        terminal: &mut dyn UpdaterTerminal,
97        context_stack: &mut ContextStack,
98        mut props: AnyProps,
99    ) {
100        let mut updater = ComponentUpdater::new(
101            self.key.clone(),
102            context_stack,
103            terminal,
104            &mut self.children,
105            &mut self.layout_style,
106        );
107        self.hooks.pre_component_update(&mut updater);
108        self.helper.update_component(
109            &mut self.component,
110            props.borrow(),
111            Hooks::new(&mut self.hooks, self.first_update),
112            &mut updater,
113        );
114        self.hooks.post_component_update(&mut updater);
115        self.first_update = false;
116        self.has_transparent_layout = updater.has_transparent_layout();
117
118        if self.has_transparent_layout {
119            if let Some(child) = self.children.iter().next() {
120                self.layout_style = child.layout_style.clone();
121            } else {
122                self.layout_style = LayoutStyle::default();
123            }
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        debug_assert_eq!(
149            children_areas.len(),
150            self.children.components.iter().count(),
151            "calc_children_areas must return one area per child"
152        );
153
154        for (child, area) in self
155            .children
156            .components
157            .iter_mut()
158            .zip(children_areas.iter())
159        {
160            drawer.area = *area;
161            child.draw(drawer);
162        }
163        self.hooks.post_component_draw(drawer);
164    }
165
166    pub(crate) fn poll_change(&mut self, cx: &mut Context) -> Poll<()> {
167        // 三路必须全部 poll,即使前一路已 Ready 也不能短路;否则 Pending 的路
168        // 无法在本轮注册 waker,后续变更会丢唤醒。
169        let component_status = self.component.poll_change(cx);
170        let children_status = self.children.poll_change(cx);
171        let hooks_status = self.hooks.poll_change(cx);
172        if component_status.is_ready() || children_status.is_ready() || hooks_status.is_ready() {
173            Poll::Ready(())
174        } else {
175            Poll::Pending
176        }
177    }
178
179    pub async fn wait(&mut self) {
180        poll_fn(|cx| self.poll_change(cx)).await;
181    }
182}
183
184impl Drop for InstantiatedComponent {
185    fn drop(&mut self) {
186        self.hooks.on_drop();
187    }
188}