Radish alpha
r
rad:z39mP9rQAaGmERfUMPULfPUi473tY
Radicle terminal user interface
Radicle
Git
lib / bin: Improve theming
Merged did:key:z6MkswQE...2C1V opened 1 year ago
7 files changed +182 -115 ba1ef1cc 8d9bc164
modified bin/commands/issue/select.rs
@@ -294,7 +294,7 @@ impl TryFrom<(&Context, &TerminalInfo)> for State {
                show_search: false,
            },
            preview: PreviewState {
-
                show: false,
+
                show: true,
                issue: items.first().cloned(),
                selected_comments,
                comment: CommentState { cursor: (0, 0) },
@@ -492,7 +492,7 @@ fn browser_page(channel: &Channel<Message>) -> Widget<State, Message> {
                if state.section == Some(Section::Browser) {
                    shortcuts = [shortcuts, [("/", "search")].to_vec()].concat()
                }
-
                [shortcuts, [("p", "preview"), ("?", "help")].to_vec()].concat()
+
                [shortcuts, [("p", "toggle preview"), ("?", "help")].to_vec()].concat()
            };

            ShortcutsProps::default()
@@ -579,8 +579,8 @@ fn issue(channel: &Channel<Message>) -> Widget<State, Message> {
        .on_update(|state| {
            SplitContainerProps::default()
                .heights([Constraint::Length(5), Constraint::Min(1)])
-
                .border_color(state.theme.border_color)
-
                .focus_border_color(state.theme.focus_border_color)
+
                .border_style(state.theme.border_style)
+
                .focus_border_style(state.theme.focus_border_style)
                .split_focus(SplitContainerFocus::Bottom)
                .to_boxed_any()
                .into()
@@ -595,6 +595,7 @@ fn issue_details(channel: &Channel<Message>) -> Widget<State, Message> {
        .on_update(|state: &State| {
            IssueDetailsProps::default()
                .issue(state.preview.issue.clone())
+
                .dim(state.theme.dim_no_focus)
                .to_boxed_any()
                .into()
        })
@@ -622,6 +623,7 @@ fn comment_tree(channel: &Channel<Message>) -> Widget<State, Message> {
                .items(root.to_vec())
                .selected(Some(selected))
                .opened(Some(opened.clone()))
+
                .dim(state.theme.dim_no_focus)
                .to_boxed_any()
                .into()
        })
@@ -666,6 +668,7 @@ fn comment(channel: &Channel<Message>) -> Widget<State, Message> {
                        .footer(Some(reactions))
                        .cursor(state.preview.comment.cursor)
                        .show_scroll_progress(true)
+
                        .dim(state.theme.dim_no_focus)
                        .to_boxed_any()
                        .into()
                }),
@@ -673,8 +676,8 @@ fn comment(channel: &Channel<Message>) -> Widget<State, Message> {
        .to_widget(tx.clone())
        .on_update(|state| {
            ContainerProps::default()
-
                .border_color(state.theme.border_color)
-
                .focus_border_color(state.theme.focus_border_color)
+
                .border_style(state.theme.border_style)
+
                .focus_border_style(state.theme.focus_border_style)
                .to_boxed_any()
                .into()
        })
@@ -705,6 +708,7 @@ fn help_page(channel: &Channel<Message>) -> Widget<State, Message> {
                    TextViewProps::default()
                        .content(help_text())
                        .cursor(state.help.cursor)
+
                        .dim(state.theme.dim_no_focus)
                        .to_boxed_any()
                        .into()
                }),
@@ -731,8 +735,8 @@ fn help_page(channel: &Channel<Message>) -> Widget<State, Message> {
        .to_widget(tx.clone())
        .on_update(|state| {
            ContainerProps::default()
-
                .border_color(state.theme.border_color)
-
                .focus_border_color(state.theme.focus_border_color)
+
                .border_style(state.theme.border_style)
+
                .focus_border_style(state.theme.focus_border_style)
                .to_boxed_any()
                .into()
        });
modified bin/commands/issue/select/ui.rs
@@ -127,8 +127,8 @@ impl Browser {

                    HeaderProps::default()
                        .columns(props.header.clone())
-
                        .border_color(state.theme.border_color)
-
                        .focus_border_color(state.theme.focus_border_color)
+
                        .border_style(state.theme.border_style)
+
                        .focus_border_style(state.theme.focus_border_style)
                        .to_boxed_any()
                        .into()
                }))
@@ -149,6 +149,7 @@ impl Browser {
                                .columns(props.columns)
                                .items(state.browser.issues())
                                .selected(state.browser.selected)
+
                                .dim(state.theme.dim_no_focus)
                                .to_boxed_any()
                                .into()
                        }),
@@ -158,16 +159,16 @@ impl Browser {

                    FooterProps::default()
                        .columns(browse_footer(&props))
-
                        .border_color(state.theme.border_color)
-
                        .focus_border_color(state.theme.focus_border_color)
+
                        .border_style(state.theme.border_style)
+
                        .focus_border_style(state.theme.focus_border_style)
                        .to_boxed_any()
                        .into()
                }))
                .to_widget(tx.clone())
                .on_update(|state| {
                    ContainerProps::default()
-
                        .border_color(state.theme.border_color)
-
                        .focus_border_color(state.theme.focus_border_color)
+
                        .border_style(state.theme.border_style)
+
                        .focus_border_style(state.theme.focus_border_style)
                        .hide_footer(BrowserProps::from(state).show_search)
                        .to_boxed_any()
                        .into()
modified bin/main.rs
@@ -1,8 +1,8 @@
mod cob;
mod commands;
mod git;
-
mod ui;
mod settings;
+
mod ui;

use std::ffi::OsString;
use std::io;
modified bin/ui/widget.rs
@@ -19,6 +19,7 @@ use super::items::IssueItem;
#[derive(Clone, Default)]
pub struct IssueDetailsProps {
    issue: Option<IssueItem>,
+
    dim: bool,
}

impl IssueDetailsProps {
@@ -26,6 +27,11 @@ impl IssueDetailsProps {
        self.issue = issue;
        self
    }
+

+
    pub fn dim(mut self, dim: bool) -> Self {
+
        self.dim = dim;
+
        self
+
    }
}

pub struct IssueDetails<S, M> {
@@ -120,6 +126,12 @@ impl<S, M> View for IssueDetails<S, M> {
                [Constraint::Length(8), Constraint::Fill(1)],
            );

+
            let table = if !render.focus && props.dim {
+
                table.dim()
+
            } else {
+
                table
+
            };
+

            frame.render_widget(table, area);
        } else {
            let center = layout::centered_rect(render.area, 50, 10);
modified src/ui/theme.rs
@@ -2,11 +2,13 @@ use ratatui::style::{Color, Style, Stylize};

#[derive(Clone, Debug)]
pub struct Theme {
-
    pub border_color: Color,
-
    pub focus_border_color: Color,
+
    pub border_style: Style,
+
    pub focus_border_style: Style,
    pub shortcuts_keys_style: Style,
    pub shortcuts_action_style: Style,
    pub textview_style: Style,
+
    pub textview_scroll_style: Style,
+
    pub textview_focus_scroll_style: Style,
    pub dim_no_focus: bool,
}

@@ -19,23 +21,27 @@ impl Default for Theme {
impl Theme {
    pub fn default_light() -> Self {
        Self {
-
            border_color: Color::Rgb(170, 170, 170),
-
            focus_border_color: Color::Black,
+
            border_style: Style::default().fg(Color::Rgb(170, 170, 170)),
+
            focus_border_style: Style::default().black(),
            shortcuts_keys_style: style::yellow(),
            shortcuts_action_style: style::reset(),
            textview_style: style::reset(),
-
            dim_no_focus: true,
+
            textview_scroll_style: style::cyan().dim(),
+
            textview_focus_scroll_style: style::cyan(),
+
            dim_no_focus: false,
        }
    }

    pub fn default_dark() -> Self {
        Self {
-
            border_color: Color::Indexed(236),
-
            focus_border_color: Color::Indexed(238),
+
            border_style: Style::default().fg(Color::Indexed(240)),
+
            focus_border_style: Style::default().fg(Color::Indexed(246)),
            shortcuts_keys_style: style::yellow().dim(),
            shortcuts_action_style: style::gray(),
            textview_style: style::reset(),
-
            dim_no_focus: true,
+
            textview_scroll_style: style::cyan().dim(),
+
            textview_focus_scroll_style: style::cyan(),
+
            dim_no_focus: false,
        }
    }
}
modified src/ui/widget/container.rs
@@ -95,8 +95,8 @@ pub struct HeaderProps<'a> {
    pub columns: Vec<Column<'a>>,
    pub cutoff: usize,
    pub cutoff_after: usize,
-
    pub border_color: Color,
-
    pub focus_border_color: Color,
+
    pub border_style: Style,
+
    pub focus_border_style: Style,
}

impl<'a> HeaderProps<'a> {
@@ -111,13 +111,13 @@ impl<'a> HeaderProps<'a> {
        self
    }

-
    pub fn border_color(mut self, color: Color) -> Self {
-
        self.border_color = color;
+
    pub fn border_style(mut self, color: Style) -> Self {
+
        self.border_style = color;
        self
    }

-
    pub fn focus_border_color(mut self, color: Color) -> Self {
-
        self.focus_border_color = color;
+
    pub fn focus_border_style(mut self, color: Style) -> Self {
+
        self.focus_border_style = color;
        self
    }
}
@@ -130,8 +130,8 @@ impl<'a> Default for HeaderProps<'a> {
            columns: vec![],
            cutoff: usize::MAX,
            cutoff_after: usize::MAX,
-
            border_color: theme.border_color,
-
            focus_border_color: theme.focus_border_color,
+
            border_style: theme.border_style,
+
            focus_border_style: theme.focus_border_style,
        }
    }
}
@@ -186,9 +186,9 @@ impl<'a: 'static, S, M> View for Header<S, M> {
            .collect::<Vec<_>>();

        let border_style = if render.focus {
-
            Style::default().fg(props.focus_border_color)
+
            props.focus_border_style
        } else {
-
            Style::default().fg(props.border_color)
+
            props.border_style
        };

        // Render header
@@ -220,8 +220,8 @@ pub struct FooterProps<'a> {
    pub columns: Vec<Column<'a>>,
    pub cutoff: usize,
    pub cutoff_after: usize,
-
    pub border_color: Color,
-
    pub focus_border_color: Color,
+
    pub border_style: Style,
+
    pub focus_border_style: Style,
}

impl<'a> FooterProps<'a> {
@@ -236,13 +236,13 @@ impl<'a> FooterProps<'a> {
        self
    }

-
    pub fn border_color(mut self, color: Color) -> Self {
-
        self.border_color = color;
+
    pub fn border_style(mut self, color: Style) -> Self {
+
        self.border_style = color;
        self
    }

-
    pub fn focus_border_color(mut self, color: Color) -> Self {
-
        self.focus_border_color = color;
+
    pub fn focus_border_style(mut self, color: Style) -> Self {
+
        self.focus_border_style = color;
        self
    }
}
@@ -255,8 +255,8 @@ impl<'a> Default for FooterProps<'a> {
            columns: vec![],
            cutoff: usize::MAX,
            cutoff_after: usize::MAX,
-
            border_color: theme.border_color,
-
            focus_border_color: theme.focus_border_color,
+
            border_style: theme.border_style,
+
            focus_border_style: theme.focus_border_style,
        }
    }
}
@@ -309,9 +309,9 @@ impl<'a: 'static, S, M> View for Footer<S, M> {
            .unwrap_or(&default);

        let border_style = if render.focus {
-
            Style::default().fg(props.focus_border_color)
+
            props.focus_border_style
        } else {
-
            Style::default().fg(props.border_color)
+
            props.border_style
        };

        let widths = props
@@ -355,8 +355,8 @@ impl<'a: 'static, S, M> View for Footer<S, M> {
#[derive(Clone)]
pub struct ContainerProps {
    hide_footer: bool,
-
    border_color: Color,
-
    focus_border_color: Color,
+
    border_style: Style,
+
    focus_border_style: Style,
}

impl Default for ContainerProps {
@@ -365,8 +365,8 @@ impl Default for ContainerProps {

        Self {
            hide_footer: false,
-
            border_color: theme.border_color,
-
            focus_border_color: theme.focus_border_color,
+
            border_style: theme.border_style,
+
            focus_border_style: theme.focus_border_style,
        }
    }
}
@@ -377,13 +377,13 @@ impl ContainerProps {
        self
    }

-
    pub fn border_color(mut self, color: Color) -> Self {
-
        self.border_color = color;
+
    pub fn border_style(mut self, color: Style) -> Self {
+
        self.border_style = color;
        self
    }

-
    pub fn focus_border_color(mut self, color: Color) -> Self {
-
        self.focus_border_color = color;
+
    pub fn focus_border_style(mut self, color: Style) -> Self {
+
        self.focus_border_style = color;
        self
    }
}
@@ -461,9 +461,9 @@ where
            .unwrap_or(&default);

        let border_style = if render.focus {
-
            Style::default().fg(props.focus_border_color)
+
            props.focus_border_style
        } else {
-
            Style::default().fg(props.border_color)
+
            props.border_style
        };

        let header_h = if self.header.is_some() { 3 } else { 0 };
@@ -524,8 +524,8 @@ pub enum SplitContainerFocus {
pub struct SplitContainerProps {
    split_focus: SplitContainerFocus,
    heights: [Constraint; 2],
-
    border_color: Color,
-
    focus_border_color: Color,
+
    border_style: Style,
+
    focus_border_style: Style,
}

impl Default for SplitContainerProps {
@@ -535,8 +535,8 @@ impl Default for SplitContainerProps {
        Self {
            split_focus: SplitContainerFocus::default(),
            heights: [Constraint::Percentage(50), Constraint::Percentage(50)],
-
            border_color: theme.border_color,
-
            focus_border_color: theme.focus_border_color,
+
            border_style: theme.border_style,
+
            focus_border_style: theme.focus_border_style,
        }
    }
}
@@ -552,13 +552,13 @@ impl SplitContainerProps {
        self
    }

-
    pub fn border_color(mut self, color: Color) -> Self {
-
        self.border_color = color;
+
    pub fn border_style(mut self, color: Style) -> Self {
+
        self.border_style = color;
        self
    }

-
    pub fn focus_border_color(mut self, color: Color) -> Self {
-
        self.focus_border_color = color;
+
    pub fn focus_border_style(mut self, color: Style) -> Self {
+
        self.focus_border_style = color;
        self
    }
}
@@ -650,9 +650,9 @@ where
            .collect::<Vec<_>>();

        let border_style = if render.focus {
-
            Style::default().fg(props.focus_border_color)
+
            props.focus_border_style
        } else {
-
            Style::default().fg(props.border_color)
+
            props.border_style
        };

        let [top_area, bottom_area] = Layout::vertical(heights).areas(render.area);
modified src/ui/widget/input.rs
@@ -1,9 +1,10 @@
use std::marker::PhantomData;

+
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use termion::event::Key;

-
use ratatui::layout::{Alignment, Constraint, Layout};
+
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Span, Text};

@@ -580,7 +581,11 @@ pub struct TextViewProps<'a> {
    /// An optional text that is rendered inside the footer bar on the bottom.
    footer: Option<Text<'a>>,
    /// The style used whenever the widget has focus.
-
    textview_style: Style,
+
    content_style: Style,
+
    /// Default scroll progress style.
+
    scroll_style: Style,
+
    /// Scroll progress style whenever the the widget has focus.
+
    focus_scroll_style: Style,
    /// Set to `true` if the content style should be dimmed whenever the widget
    /// has no focus.
    dim: bool,
@@ -618,8 +623,18 @@ impl<'a> TextViewProps<'a> {
        self
    }

-
    pub fn textview_style(mut self, style: Style) -> Self {
-
        self.textview_style = style;
+
    pub fn content_style(mut self, style: Style) -> Self {
+
        self.content_style = style;
+
        self
+
    }
+

+
    pub fn scroll_style(mut self, style: Style) -> Self {
+
        self.scroll_style = style;
+
        self
+
    }
+

+
    pub fn focus_scroll_style(mut self, style: Style) -> Self {
+
        self.focus_scroll_style = style;
        self
    }

@@ -639,7 +654,9 @@ impl<'a> Default for TextViewProps<'a> {
            handle_keys: true,
            show_scroll_progress: false,
            footer: None,
-
            textview_style: theme.textview_style,
+
            content_style: theme.textview_style,
+
            scroll_style: theme.textview_scroll_style,
+
            focus_scroll_style: theme.textview_focus_scroll_style,
            dim: false,
        }
    }
@@ -707,6 +724,67 @@ impl<S, M> TextView<S, M> {
    fn end(&mut self, len: usize, page_size: usize) {
        self.state.cursor.0 = len.saturating_sub(page_size);
    }
+

+
    fn update_area(&mut self, area: Rect) {
+
        self.area = (area.height, area.width);
+
    }
+

+
    fn render_content(&self, frame: &mut Frame, props: &TextViewProps, render: &RenderProps) {
+
        let content_style = if !render.focus && props.dim {
+
            props.content_style.dim()
+
        } else {
+
            props.content_style
+
        };
+

+
        let content = Paragraph::new(props.content.clone())
+
            .style(content_style)
+
            .scroll((self.state.cursor.0 as u16, self.state.cursor.1 as u16));
+

+
        frame.render_widget(content, render.area);
+
    }
+

+
    fn render_footer(
+
        &self,
+
        frame: &mut Frame,
+
        props: &TextViewProps,
+
        render: &RenderProps,
+
        content_height: u16,
+
    ) {
+
        let [text_area, scroll_area] =
+
            Layout::horizontal([Constraint::Min(1), Constraint::Length(10)]).areas(render.area);
+

+
        let scroll_style = if render.focus {
+
            props.focus_scroll_style
+
        } else {
+
            props.scroll_style
+
        };
+

+
        let mut scroll = vec![];
+
        if props.show_scroll_progress {
+
            let content_len = props.content.lines.len();
+
            let scroll_progress = utils::scroll::percent_absolute(
+
                self.state.cursor.0,
+
                content_len,
+
                content_height.into(),
+
            );
+
            if (content_height as usize) < content_len {
+
                // vec![Span::styled(format!("All / {}", content_len), scroll_style)]
+
                scroll = vec![Span::styled(format!("{}%", scroll_progress), scroll_style)];
+
            }
+
        }
+

+
        frame.render_widget(
+
            props
+
                .footer
+
                .as_ref()
+
                .cloned()
+
                .unwrap_or_default()
+
                .alignment(Alignment::Left)
+
                .dim(),
+
            text_area,
+
        );
+
        frame.render_widget(Line::from(scroll).alignment(Alignment::Right), scroll_area);
+
    }
}

impl<S, M> View for TextView<S, M>
@@ -788,61 +866,27 @@ where
        let props = props
            .and_then(|props| props.inner_ref::<TextViewProps>())
            .unwrap_or(&default);
+
        let render_footer = props.show_scroll_progress || props.footer.is_some();

        let [area] = Layout::default()
            .constraints([Constraint::Min(1)])
            .horizontal_margin(1)
            .areas(render.area);

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

-
        let style = if !render.focus && props.dim {
-
            props.textview_style.dim()
-
        } else {
-
            props.textview_style
-
        };
-

-
        let content = ratatui::widgets::Paragraph::new(props.content.clone())
-
            .style(style)
-
            .scroll((self.state.cursor.0 as u16, self.state.cursor.1 as u16));
-

-
        let scroll_progress = utils::scroll::percent_absolute(
-
            self.state.cursor.0,
-
            props.content.lines.len(),
-
            content_area.height.into(),
-
        );
+
        if render_footer {
+
            let [content_area, footer_area] = Layout::vertical([
+
                Constraint::Min(1),
+
                Constraint::Length(if render_footer { 1 } else { 0 }),
+
            ])
+
            .areas(area);

-
        let progress_info = if props.show_scroll_progress {
-
            vec![Span::styled(
-
                format!("{}%", scroll_progress),
-
                Style::default().dim(),
-
            )]
+
            self.render_content(frame, props, &render.clone().area(content_area));
+
            self.render_footer(frame, props, &render.area(footer_area), content_area.height);
+
            self.update_area(content_area);
        } else {
-
            vec![]
-
        };
-

-
        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),
-
            footer_area,
-
        );
-

-
        self.area = (content_area.height, content_area.width);
+
            self.render_content(frame, props, &render);
+
            self.update_area(render.area);
+
        }
    }

    fn view_state(&self) -> Option<ViewState> {