ratatui_kit/component/
instantiated_component.rs1use 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 self.hooks.pre_component_draw(drawer);
140
141 self.component.draw(drawer);
143
144 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 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}