Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
Various library improvements
Merged did:key:z6MkswQE...2C1V opened 1 year ago
4 files changed +71 -13 2a8825dd bc2378e1
modified src/ui/widget.rs
@@ -13,7 +13,10 @@ use termion::event::Key;

use ratatui::prelude::*;

-
use self::input::{TextAreaState, TextViewState};
+
use self::{
+
    container::SectionGroupState,
+
    input::{TextAreaState, TextViewState},
+
};

pub type BoxedView<S, M> = Box<dyn View<State = S, Message = M>>;
pub type UpdateCallback<S> = fn(&S) -> ViewProps;
@@ -69,6 +72,7 @@ pub enum ViewState {
    Tree(Vec<String>),
    TextView(TextViewState),
    TextArea(TextAreaState),
+
    SectionGroup(SectionGroupState),
}

impl ViewState {
@@ -107,6 +111,13 @@ impl ViewState {
        }
    }

+
    pub fn unwrap_section_group(&self) -> Option<SectionGroupState> {
+
        match self {
+
            ViewState::SectionGroup(state) => Some(state.clone()),
+
            _ => None,
+
        }
+
    }
+

    pub fn unwrap_tree(&self) -> Option<Vec<String>> {
        match self {
            ViewState::Tree(value) => Some(value.clone().to_vec()),
@@ -119,21 +130,25 @@ impl ViewState {
pub enum PredefinedLayout {
    #[default]
    None,
-
    Expandable3,
+
    Expandable3 {
+
        left_only: bool,
+
    },
}

impl PredefinedLayout {
    pub fn split(&self, area: Rect) -> Rc<[Rect]> {
        match self {
-
            Self::Expandable3 => {
-
                if area.width <= 140 {
+
            Self::Expandable3 { left_only } => {
+
                if *left_only {
+
                    [area].into()
+
                } else if area.width <= 140 {
                    let [left, right] = Layout::horizontal([
                        Constraint::Percentage(50),
                        Constraint::Percentage(50),
                    ])
                    .areas(area);
                    let [right_top, right_bottom] =
-
                        Layout::vertical([Constraint::Percentage(50), Constraint::Percentage(50)])
+
                        Layout::vertical([Constraint::Percentage(65), Constraint::Percentage(35)])
                            .areas(right);

                    [left, right_top, right_bottom].into()
modified src/ui/widget/container.rs
@@ -10,7 +10,7 @@ use crate::ui::ext::{FooterBlock, FooterBlockType, HeaderBlock};
use crate::ui::theme::style;
use crate::ui::{RENDER_WIDTH_LARGE, RENDER_WIDTH_MEDIUM, RENDER_WIDTH_SMALL};

-
use super::{PredefinedLayout, RenderProps, View, ViewProps, Widget};
+
use super::{PredefinedLayout, RenderProps, View, ViewProps, ViewState, Widget};

#[derive(Clone, Debug, Default)]
pub struct ColumnView {
@@ -581,14 +581,17 @@ where
    }
}

-
#[derive(Clone)]
+
#[derive(Clone, Debug)]
pub struct SectionGroupState {
    /// Index of currently focused section.
-
    focus: Option<usize>,
+
    pub focus: Option<usize>,
}

#[derive(Clone, Default)]
pub struct SectionGroupProps {
+
    /// Index of currently focused section. If set, it will override the widgets'
+
    /// internal state.
+
    focus: Option<usize>,
    /// If this pages' keys should be handled.
    handle_keys: bool,
    /// Section layout
@@ -605,6 +608,11 @@ impl SectionGroupProps {
        self.layout = layout;
        self
    }
+

+
    pub fn focus(mut self, focus: Option<usize>) -> Self {
+
        self.focus = focus;
+
        self
+
    }
}

pub struct SectionGroup<S, M> {
@@ -685,10 +693,19 @@ where
        None
    }

-
    fn update(&mut self, _props: Option<&ViewProps>, state: &Self::State) {
+
    fn update(&mut self, props: Option<&ViewProps>, state: &Self::State) {
+
        let default = SectionGroupProps::default();
+
        let props = props
+
            .and_then(|props| props.inner_ref::<SectionGroupProps>())
+
            .unwrap_or(&default);
+

        for section in &mut self.sections {
            section.update(state);
        }
+

+
        if props.focus.is_some() && props.focus != self.state.focus {
+
            self.state.focus = props.focus;
+
        }
    }

    fn render(&mut self, props: Option<&ViewProps>, render: RenderProps, frame: &mut Frame) {
@@ -711,4 +728,8 @@ where
            }
        }
    }
+

+
    fn view_state(&self) -> Option<super::ViewState> {
+
        Some(ViewState::SectionGroup(self.state.clone()))
+
    }
}
modified src/ui/widget/input.rs
@@ -548,6 +548,8 @@ pub struct TextViewProps<'a> {
    handle_keys: bool,
    /// If this widget should render its scroll progress. Default: `false`.
    show_scroll_progress: bool,
+
    /// An optional text that is rendered inside the footer bar on the bottom.
+
    footer: Option<Text<'a>>,
}

impl<'a> TextViewProps<'a> {
@@ -559,6 +561,14 @@ impl<'a> TextViewProps<'a> {
        self
    }

+
    pub fn footer<T>(mut self, footer: Option<T>) -> Self
+
    where
+
        T: Into<Text<'a>>,
+
    {
+
        self.footer = footer.map(|f| f.into());
+
        self
+
    }
+

    pub fn cursor(mut self, cursor: (usize, usize)) -> Self {
        self.cursor = cursor;
        self
@@ -582,6 +592,7 @@ impl<'a> Default for TextViewProps<'a> {
            cursor: (0, 0),
            handle_keys: true,
            show_scroll_progress: false,
+
            footer: None,
        }
    }
}
@@ -735,9 +746,10 @@ where
            .horizontal_margin(1)
            .areas(render.area);

-
        let [content_area, progress_area] = Layout::vertical([
+
        let render_footer = props.show_scroll_progress || props.footer.is_some();
+
        let [content_area, footer_area] = Layout::vertical([
            Constraint::Min(1),
-
            Constraint::Length(if props.show_scroll_progress { 1 } else { 0 }),
+
            Constraint::Length(if render_footer { 1 } else { 0 }),
        ])
        .areas(area);

@@ -768,8 +780,18 @@ where

        frame.render_widget(content, content_area);
        frame.render_widget(
+
            props
+
                .footer
+
                .as_ref()
+
                .cloned()
+
                .unwrap_or_default()
+
                .alignment(Alignment::Left)
+
                .dim(),
+
            footer_area,
+
        );
+
        frame.render_widget(
            Line::from(progress_info).alignment(Alignment::Right),
-
            progress_area,
+
            footer_area,
        );

        self.area = (content_area.height, content_area.width);
modified src/ui/widget/utils.rs
@@ -20,7 +20,7 @@ pub mod scroll {
        let t = len.saturating_sub(1) as f64;
        let v = y / (t - h) * 100_f64;

-
        std::cmp::max(0, std::cmp::min(100, v as usize))
+
        (v as usize).clamp(0, 100)
    }

    fn map_range(from: (f64, f64), to: (f64, f64), value: f64) -> f64 {