Skip to main content

tui_realm_stdlib/components/
container.rs

1//! `Container` represents an empty container where you can put other components into it.
2
3use tuirealm::command::{Cmd, CmdResult};
4use tuirealm::component::Component;
5use tuirealm::props::{
6    AttrValue, Attribute, Borders, Color, Layout, Props, QueryResult, Style, TextModifiers, Title,
7};
8use tuirealm::ratatui::Frame;
9use tuirealm::ratatui::layout::Rect;
10use tuirealm::state::State;
11
12use crate::prop_ext::CommonProps;
13
14// -- Component
15
16/// `Container` represents an empty container where you can put other components into it.
17///
18/// It will render components based on how you defined the layout.
19/// The way it updates properties is usually by assigning the attributes to all the children components, but
20/// when defining the component you can override these behaviours by implementing `attr()` yourself and directly accessing `children`'s `attr()`.
21/// By default it will forward `Commands' to all the children and will return a `CmdResult::Batch` with all the results.
22#[must_use]
23pub struct Container {
24    common: CommonProps,
25    props: Props,
26    /// Container children
27    pub children: Vec<Box<dyn Component>>,
28}
29
30impl Default for Container {
31    fn default() -> Self {
32        Self {
33            common: CommonProps {
34                border: Some(Borders::default()),
35                ..CommonProps::default()
36            },
37            props: Props::default(),
38            children: Vec::new(),
39        }
40    }
41}
42
43impl Container {
44    /// Set the main foreground color. This may get overwritten by individual text styles.
45    pub fn foreground(mut self, fg: Color) -> Self {
46        self.attr(Attribute::Foreground, AttrValue::Color(fg));
47        self
48    }
49
50    /// Set the main background color. This may get overwritten by individual text styles.
51    pub fn background(mut self, bg: Color) -> Self {
52        self.attr(Attribute::Background, AttrValue::Color(bg));
53        self
54    }
55
56    /// Set the main text modifiers. This may get overwritten by individual text styles.
57    pub fn modifiers(mut self, m: TextModifiers) -> Self {
58        self.attr(Attribute::TextProps, AttrValue::TextModifiers(m));
59        self
60    }
61
62    /// Set the main style. This may get overwritten by individual text styles.
63    ///
64    /// This option will overwrite any previous [`foreground`](Self::foreground), [`background`](Self::background) and [`modifiers`](Self::modifiers)!
65    pub fn style(mut self, style: Style) -> Self {
66        self.attr(Attribute::Style, AttrValue::Style(style));
67        self
68    }
69
70    /// Set a custom style for the border when the component is unfocused.
71    pub fn inactive(mut self, s: Style) -> Self {
72        self.attr(Attribute::UnfocusedBorderStyle, AttrValue::Style(s));
73        self
74    }
75
76    /// Add a border to the component.
77    pub fn borders(mut self, b: Borders) -> Self {
78        self.attr(Attribute::Borders, AttrValue::Borders(b));
79        self
80    }
81
82    /// Add a title to the component.
83    pub fn title<T: Into<Title>>(mut self, title: T) -> Self {
84        self.attr(Attribute::Title, AttrValue::Title(title.into()));
85        self
86    }
87
88    /// Set a ratatui Layout to use for all the child components.
89    ///
90    /// If this is unset, nothing gets drawn!
91    pub fn layout(mut self, layout: Layout) -> Self {
92        self.attr(Attribute::Layout, AttrValue::Layout(layout));
93        self
94    }
95
96    /// Set the children Components this container contains.
97    pub fn children(mut self, children: Vec<Box<dyn Component>>) -> Self {
98        self.children = children;
99        self
100    }
101}
102
103impl Component for Container {
104    fn view(&mut self, render: &mut Frame, mut area: Rect) {
105        if !self.common.display {
106            return;
107        }
108
109        if let Some(block) = self.common.get_block() {
110            let inner = block.inner(area);
111            // Render block
112            render.render_widget(block, area);
113            area = inner;
114        }
115
116        // Render children
117        if let Some(layout) = self
118            .props
119            .get(Attribute::Layout)
120            .and_then(AttrValue::as_layout)
121        {
122            // make chunks
123            let chunks = layout.chunks(area);
124            // iter chunks
125            for (i, chunk) in chunks.into_iter().enumerate() {
126                if let Some(child) = self.children.get_mut(i) {
127                    child.view(render, chunk);
128                }
129            }
130        }
131    }
132
133    fn query<'a>(&'a self, attr: Attribute) -> Option<QueryResult<'a>> {
134        if let Some(value) = self.common.get_for_query(attr) {
135            return Some(value);
136        }
137
138        self.props.get_for_query(attr)
139    }
140
141    fn attr(&mut self, attr: Attribute, value: AttrValue) {
142        if let Some(value) = self.common.set(attr, value.clone()) {
143            self.props.set(attr, value);
144        }
145
146        // dont patch borders and title to all children
147        if matches!(attr, Attribute::Borders | Attribute::Title) {
148            return;
149        }
150
151        // TODO: how do you control patching things to children? For example change the title / border of a specific child?
152        // Patch attribute to children
153        self.children
154            .iter_mut()
155            .for_each(|x| x.attr(attr, value.clone()));
156    }
157
158    fn state(&self) -> State {
159        State::None
160    }
161
162    fn perform(&mut self, cmd: Cmd) -> CmdResult {
163        // TODO: send commands to a specific child?
164
165        // Send command to children and return batch
166        CmdResult::Batch(self.children.iter_mut().map(|x| x.perform(cmd)).collect())
167    }
168}
169
170#[cfg(test)]
171mod tests {
172
173    use pretty_assertions::assert_eq;
174    use tuirealm::props::HorizontalAlignment;
175
176    use super::*;
177
178    #[test]
179    fn test_components_paragraph() {
180        let component = Container::default()
181            .background(Color::Blue)
182            .foreground(Color::Red)
183            .title(Title::from("title").alignment(HorizontalAlignment::Center));
184        // Get value
185        assert_eq!(component.state(), State::None);
186    }
187}