Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
lib: Introduce vertically split container
Erik Kundt committed 1 year ago
commit 17e197bdaa303c17bfdac3a6a3f6401bf20fcca8
parent f12b1fb97d9680285493f312ccb83104adc1f056
1 file changed +148 -0
modified src/ui/widget/container.rs
@@ -379,6 +379,154 @@ where
    }
}

+
#[derive(Clone, Default)]
+
pub enum SplitContainerFocus {
+
    #[default]
+
    Top,
+
    Bottom,
+
}
+

+
#[derive(Clone, Default)]
+
pub struct SplitContainerProps {
+
    split_focus: SplitContainerFocus,
+
    heights: [Constraint; 2],
+
}
+

+
impl SplitContainerProps {
+
    pub fn split_focus(mut self, split_focus: SplitContainerFocus) -> Self {
+
        self.split_focus = split_focus;
+
        self
+
    }
+

+
    pub fn heights(mut self, heights: [Constraint; 2]) -> Self {
+
        self.heights = heights;
+
        self
+
    }
+
}
+

+
pub struct SplitContainer<S, M> {
+
    /// Container top
+
    top: Option<Widget<S, M>>,
+
    /// Content bottom
+
    bottom: Option<Widget<S, M>>,
+
}
+

+
impl<S, M> Default for SplitContainer<S, M> {
+
    fn default() -> Self {
+
        Self {
+
            top: None,
+
            bottom: None,
+
        }
+
    }
+
}
+

+
impl<S, M> SplitContainer<S, M> {
+
    pub fn top(mut self, top: Widget<S, M>) -> Self {
+
        self.top = Some(top);
+
        self
+
    }
+

+
    pub fn bottom(mut self, bottom: Widget<S, M>) -> Self {
+
        self.bottom = Some(bottom);
+
        self
+
    }
+
}
+

+
impl<S, M> View for SplitContainer<S, M>
+
where
+
    S: 'static,
+
    M: 'static,
+
{
+
    type Message = M;
+
    type State = S;
+

+
    fn handle_event(&mut self, props: Option<&ViewProps>, key: Key) -> Option<Self::Message> {
+
        let default = SplitContainerProps::default();
+
        let props = props
+
            .and_then(|props| props.inner_ref::<SplitContainerProps>())
+
            .unwrap_or(&default);
+

+
        match props.split_focus {
+
            SplitContainerFocus::Top => {
+
                if let Some(top) = self.top.as_mut() {
+
                    top.handle_event(key);
+
                }
+
            }
+
            SplitContainerFocus::Bottom => {
+
                if let Some(bottom) = self.bottom.as_mut() {
+
                    bottom.handle_event(key);
+
                }
+
            }
+
        }
+

+
        None
+
    }
+

+
    fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) {
+
        if let Some(top) = self.top.as_mut() {
+
            top.update(state);
+
        }
+

+
        if let Some(bottom) = self.bottom.as_mut() {
+
            bottom.update(state);
+
        }
+
    }
+

+
    fn render(&self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
+
        let default = SplitContainerProps::default();
+
        let props = props
+
            .and_then(|props| props.inner_ref::<SplitContainerProps>())
+
            .unwrap_or(&default);
+

+
        let heights = props
+
            .heights
+
            .iter()
+
            .map(|c| {
+
                if let Constraint::Length(l) = c {
+
                    Constraint::Length(l + 2)
+
                } else {
+
                    *c
+
                }
+
            })
+
            .collect::<Vec<_>>();
+

+
        let [top_area, bottom_area] = Layout::vertical(heights).areas(render.area);
+

+
        if let Some(top) = self.top.as_ref() {
+
            let block = HeaderBlock::default()
+
                .borders(Borders::ALL)
+
                .border_style(style::border(render.focus))
+
                .border_type(BorderType::Rounded);
+

+
            frame.render_widget(block, top_area);
+

+
            let [top_area] = Layout::default()
+
                .direction(Direction::Vertical)
+
                .constraints(vec![Constraint::Min(1)])
+
                .vertical_margin(1)
+
                .horizontal_margin(2)
+
                .areas(top_area);
+
            top.render(RenderProps::from(top_area).focus(render.focus), frame)
+
        }
+

+
        if let Some(bottom) = self.bottom.as_ref() {
+
            let block = Block::default()
+
                .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM)
+
                .border_style(style::border(render.focus))
+
                .border_type(BorderType::Rounded);
+

+
            frame.render_widget(block, bottom_area);
+

+
            let [bottom_area, _] = Layout::default()
+
                .direction(Direction::Vertical)
+
                .constraints(vec![Constraint::Min(1), Constraint::Length(1)])
+
                .horizontal_margin(2)
+
                .areas(bottom_area);
+
            bottom.render(RenderProps::from(bottom_area).focus(render.focus), frame)
+
        }
+
    }
+
}
+

#[derive(Clone)]
pub struct SectionGroupState {
    /// Index of currently focused section.