Radish alpha
r
Radicle terminal user interface
Radicle
Git (anonymous pull)
Log in to clone via SSH
Add context bar to issue page
Erik Kundt committed 2 years ago
commit 682400f7cb4102431962a87fb8946d53ab103586
parent cd5522f6a936edc658f7a5b3e27a5e942c3d16e0
9 files changed +126 -27
modified src/app.rs
@@ -46,6 +46,7 @@ pub enum IssueCid {
    Header,
    List,
    Details,
+
    Context,
    Shortcuts,
}

modified src/app/page.rs
@@ -276,13 +276,31 @@ impl ViewPage for HomeView {
///
pub struct IssuePage {
    issue: (IssueId, Issue),
+
    active_component: IssueCid,
    shortcuts: HashMap<IssueCid, Widget<Shortcuts>>,
}

impl IssuePage {
    pub fn new(theme: Theme, issue: (IssueId, Issue)) -> Self {
        let shortcuts = Self::build_shortcuts(&theme);
-
        IssuePage { issue, shortcuts }
+
        let active_component = IssueCid::List;
+

+
        Self {
+
            issue,
+
            active_component,
+
            shortcuts,
+
        }
+
    }
+

+
    fn activate(
+
        &mut self,
+
        app: &mut Application<Cid, Message, NoUserEvent>,
+
        cid: IssueCid,
+
    ) -> Result<()> {
+
        self.active_component = cid;
+
        app.active(&Cid::Issue(self.active_component.clone()))?;
+

+
        Ok(())
    }

    fn build_shortcuts(theme: &Theme) -> HashMap<IssueCid, Widget<Shortcuts>> {
@@ -316,6 +334,46 @@ impl IssuePage {
        .collect()
    }

+
    fn update_context(
+
        &self,
+
        app: &mut Application<Cid, Message, NoUserEvent>,
+
        context: &Context,
+
        theme: &Theme,
+
        cid: IssueCid,
+
    ) -> Result<()> {
+
        use tuirealm::State;
+

+
        let context = match cid {
+
            IssueCid::List => {
+
                let state = app.state(&Cid::Issue(IssueCid::List))?;
+
                let progress = match state {
+
                    State::Tup2((StateValue::Usize(step), StateValue::Usize(total))) => {
+
                        Progress::Step(step.saturating_add(1), total)
+
                    }
+
                    _ => Progress::None,
+
                };
+
                let context = widget::issue::browse_context(context, theme, progress);
+
                Some(context)
+
            }
+
            IssueCid::Details => {
+
                let state = app.state(&Cid::Issue(IssueCid::Details))?;
+
                let progress = match state {
+
                    State::One(StateValue::Usize(scroll)) => Progress::Percentage(scroll),
+
                    _ => Progress::None,
+
                };
+
                let context = widget::issue::description_context(context, theme, progress);
+
                Some(context)
+
            }
+
            _ => None,
+
        };
+

+
        if let Some(context) = context {
+
            app.remount(Cid::Issue(IssueCid::Context), context.to_boxed(), vec![])?;
+
        }
+

+
        Ok(())
+
    }
+

    fn update_shortcuts(
        &self,
        app: &mut Application<Cid, Message, NoUserEvent>,
@@ -356,8 +414,10 @@ impl ViewPage for IssuePage {
        app.remount(Cid::Issue(IssueCid::List), list, vec![])?;
        app.remount(Cid::Issue(IssueCid::Details), details, vec![])?;

-
        app.active(&Cid::Issue(IssueCid::List))?;
-
        self.update_shortcuts(app, IssueCid::List)?;
+
        app.active(&Cid::Issue(self.active_component.clone()))?;
+

+
        self.update_shortcuts(app, self.active_component.clone())?;
+
        self.update_context(app, context, theme, self.active_component.clone())?;

        Ok(())
    }
@@ -366,6 +426,7 @@ impl ViewPage for IssuePage {
        app.umount(&Cid::Issue(IssueCid::Header))?;
        app.umount(&Cid::Issue(IssueCid::List))?;
        app.umount(&Cid::Issue(IssueCid::Details))?;
+
        app.umount(&Cid::Issue(IssueCid::Context))?;
        app.umount(&Cid::Issue(IssueCid::Shortcuts))?;
        Ok(())
    }
@@ -393,12 +454,14 @@ impl ViewPage for IssuePage {
                }
            }
            Message::Issue(IssueMessage::Focus(cid)) => {
-
                app.active(&Cid::Issue(cid.clone()))?;
-
                self.update_shortcuts(app, cid)?;
+
                self.activate(app, cid)?;
+
                self.update_shortcuts(app, self.active_component.clone())?;
            }
            _ => {}
        }

+
        self.update_context(app, context, theme, self.active_component.clone())?;
+

        Ok(None)
    }

@@ -410,6 +473,7 @@ impl ViewPage for IssuePage {
        app.view(&Cid::Issue(IssueCid::Header), frame, layout.header);
        app.view(&Cid::Issue(IssueCid::List), frame, layout.left);
        app.view(&Cid::Issue(IssueCid::Details), frame, layout.right);
+
        app.view(&Cid::Issue(IssueCid::Context), frame, layout.context);
        app.view(&Cid::Issue(IssueCid::Shortcuts), frame, layout.shortcuts);
    }

modified src/ui/layout.rs
@@ -19,6 +19,7 @@ pub struct IssuePage {
    pub header: Rect,
    pub left: Rect,
    pub right: Rect,
+
    pub context: Rect,
    pub shortcuts: Rect,
}

@@ -207,18 +208,20 @@ pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {

pub fn issue_page(area: Rect, shortcuts_h: u16) -> IssuePage {
    let header_h = 3u16;
+
    let context_h = 1u16;
+
    let margin_h = 1u16;
    let content_h = area
        .height
-
        .saturating_sub(header_h)
-
        .saturating_sub(shortcuts_h);
+
        .saturating_sub(header_h.saturating_add(context_h.saturating_add(shortcuts_h)));

    let root = Layout::default()
        .direction(Direction::Vertical)
-
        .horizontal_margin(1)
+
        .horizontal_margin(margin_h)
        .constraints(
            [
                Constraint::Length(header_h),
                Constraint::Length(content_h),
+
                Constraint::Length(context_h),
                Constraint::Length(shortcuts_h),
            ]
            .as_ref(),
@@ -234,6 +237,7 @@ pub fn issue_page(area: Rect, shortcuts_h: u16) -> IssuePage {
        header: root[0],
        left: split[0],
        right: split[1],
-
        shortcuts: root[2],
+
        context: root[2],
+
        shortcuts: root[3],
    }
}
modified src/ui/theme.rs
@@ -103,7 +103,7 @@ pub fn default_dark() -> Theme {
            context_bg: COLOR_DEFAULT_DARKEST,
            context_light: Color::Gray,
            context_dark: COLOR_DEFAULT_DARK,
-
            context_badge_bg: Color::LightRed,
+
            context_badge_bg: Color::Magenta,
            context_color_fg: Color::Cyan,
            container_border_fg: COLOR_DEFAULT_DARKEST,
            container_border_focus_fg: COLOR_DEFAULT_DARK,
modified src/ui/widget/common/container.rs
@@ -376,7 +376,7 @@ impl WidgetComponent for Container {
    }

    fn state(&self) -> State {
-
        State::None
+
        self.component.state()
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
@@ -449,7 +449,7 @@ impl WidgetComponent for LabeledContainer {
    }

    fn state(&self) -> State {
-
        State::None
+
        self.component.state()
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
modified src/ui/widget/common/context.rs
@@ -200,7 +200,9 @@ pub fn bar(
    label_3: &str,
    label_4: &str,
) -> Widget<ContextBar> {
-
    let context = super::label(&format!(" {label_0} ")).background(theme.colors.context_badge_bg);
+
    let context = super::label(&format!(" {label_0} "))
+
        .foreground(theme.colors.context_bg)
+
        .background(theme.colors.context_badge_bg);
    let id = super::label(&format!(" {label_1} "))
        .foreground(theme.colors.context_color_fg)
        .background(theme.colors.context_bg);
modified src/ui/widget/common/label.rs
@@ -86,15 +86,23 @@ pub struct Textarea {
    theme: Theme,
    /// The scroll offset.
    offset: usize,
+
    /// The current line count.
+
    len: usize,
+
    /// The current display height.
+
    height: usize,
    /// The percentage scrolled.
    scroll_percent: usize,
}

impl Textarea {
+
    pub const PROP_DISPLAY_PROGRESS: &str = "display-progress";
+

    pub fn new(theme: Theme) -> Self {
        Self {
            theme,
            offset: 0,
+
            len: 0,
+
            height: 0,
            scroll_percent: 0,
        }
    }
@@ -123,6 +131,12 @@ impl WidgetComponent for Textarea {
        let fg = properties
            .get_or(Attribute::Foreground, AttrValue::Color(Color::Reset))
            .unwrap_color();
+
        let display_progress = properties
+
            .get_or(
+
                Attribute::Custom(Self::PROP_DISPLAY_PROGRESS),
+
                AttrValue::Flag(false),
+
            )
+
            .unwrap_flag();

        let content = properties
            .get_or(Attribute::Content, AttrValue::String(String::default()))
@@ -146,8 +160,8 @@ impl WidgetComponent for Textarea {
        // needs be done before wrapping. So this should rather wrap styled text
        // spans than plain text.
        let body = textwrap::wrap(&content, area.width.saturating_sub(2) as usize);
-
        let len = body.len();
-
        let height = layout[0].height.saturating_sub(1);
+
        self.len = body.len();
+
        self.height = (layout[0].height - 1) as usize;

        let body: String = body.iter().map(|line| format!("{}\n", line)).collect();

@@ -156,19 +170,21 @@ impl WidgetComponent for Textarea {
            .style(Style::default().fg(fg));
        frame.render_widget(paragraph, layout[0]);

-
        self.scroll_percent = Self::scroll_percent(self.offset, len, height as usize);
+
        self.scroll_percent = Self::scroll_percent(self.offset, self.len, self.height);

-
        let progress = Spans::from(vec![Span::styled(
-
            format!("{} %", self.scroll_percent),
-
            Style::default().fg(highlight_color),
-
        )]);
+
        if display_progress {
+
            let progress = Spans::from(vec![Span::styled(
+
                format!("{} %", self.scroll_percent),
+
                Style::default().fg(highlight_color),
+
            )]);

-
        let progress = Paragraph::new(progress).alignment(Alignment::Right);
-
        frame.render_widget(progress, layout[1]);
+
            let progress = Paragraph::new(progress).alignment(Alignment::Right);
+
            frame.render_widget(progress, layout[1]);
+
        }
    }

    fn state(&self) -> State {
-
        State::One(StateValue::Usize(self.offset))
+
        State::One(StateValue::Usize(self.scroll_percent))
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
@@ -177,11 +193,13 @@ impl WidgetComponent for Textarea {
        match cmd {
            Cmd::Scroll(Direction::Up) => {
                self.offset = self.offset.saturating_sub(1);
+
                self.scroll_percent = Self::scroll_percent(self.offset, self.len, self.height);
                CmdResult::None
            }
            Cmd::Scroll(Direction::Down) => {
                if self.scroll_percent < 100 {
                    self.offset = self.offset.saturating_add(1);
+
                    self.scroll_percent = Self::scroll_percent(self.offset, self.len, self.height);
                }
                CmdResult::None
            }
modified src/ui/widget/common/list.rs
@@ -349,7 +349,9 @@ where
    }

    fn state(&self) -> State {
-
        State::None
+
        let selected = self.state.selected().unwrap_or_default();
+
        let len = self.items.len();
+
        State::Tup2((StateValue::Usize(selected), StateValue::Usize(len)))
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
modified src/ui/widget/issue.rs
@@ -70,7 +70,7 @@ impl WidgetComponent for LargeList {
    }

    fn state(&self) -> State {
-
        State::None
+
        self.list.state()
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
@@ -197,7 +197,7 @@ impl WidgetComponent for IssueDetails {
    }

    fn state(&self) -> State {
-
        State::None
+
        self.description.state()
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
@@ -236,7 +236,7 @@ impl WidgetComponent for CommentBody {
    }

    fn state(&self) -> State {
-
        State::None
+
        self.textarea.state()
    }

    fn perform(&mut self, _properties: &Props, cmd: Cmd) -> CmdResult {
@@ -298,3 +298,11 @@ pub fn browse_context(context: &Context, theme: &Theme, progress: Progress) -> W
        &progress.to_string(),
    )
}
+

+
pub fn description_context(
+
    _context: &Context,
+
    theme: &Theme,
+
    progress: Progress,
+
) -> Widget<ContextBar> {
+
    common::context::bar(theme, "Show", "", "", "", &progress.to_string())
+
}