Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed item on top #21

Open
github-actions bot opened this issue Nov 29, 2021 · 0 comments
Open

Fixed item on top #21

github-actions bot opened this issue Nov 29, 2021 · 0 comments
Labels

Comments

@github-actions
Copy link

Fixed item on top

Useful for table header

Append to vec on each render

fn test_movable_list() {

let items = &["Test1", "测试1", "[ABCD] 🇺🇲 测试 符号 106"].into_iter().map(|x| x.);

assert_eq!()

}

use clashctl_interactive::{EndlessSelf, Noop, SortMethod};
use tui::widgets::Widget;
use tui::{
    layout::Rect,
    style::{Color, Modifier, Style},
    text::Span,
    widgets::{List, ListItem},
};

use crate::{
    components::{
        Footer, FooterItem, FooterWidget, MovableListItem, MovableListManage, MovableListState,
    },
    spans_window_owned, tagged_footer,
    utils::{get_block, get_focused_block, get_text_style},
};

// TODO Fixed item on top
// Useful for table header
// Append to vec on each render
#[derive(Clone, Debug)]
pub struct MovableList<'a, T, S = Noop>
where
    T: MovableListItem<'a>,
    S: Default,
{
    pub(super) title: String,
    pub(super) state: &'a MovableListState<'a, T, S>,
}

impl<'a, T, S> MovableList<'a, T, S>
where
    S: SortMethod<T> + EndlessSelf + Default + ToString,
    T: MovableListItem<'a>,
    MovableListState<'a, T, S>: MovableListManage,
{
    pub fn new<TITLE: Into<String>>(title: TITLE, state: &'a MovableListState<'a, T, S>) -> Self {
        Self {
            state,
            title: title.into(),
        }
    }

    fn render_footer(&self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) {
        let mut footer = Footer::default();
        let pos = self.state.current_pos();

        let sort_str = self.state.sort.to_string();

        footer.push_right(FooterItem::span(Span::styled(
            format!(" Ln {}, Col {} ", pos.y, pos.x),
            Style::default()
                .fg(if pos.hold { Color::Green } else { Color::Blue })
                .add_modifier(Modifier::REVERSED),
        )));

        if pos.hold {
            let style = Style::default()
                .fg(Color::Green)
                .add_modifier(Modifier::REVERSED);

            footer.push_left(FooterItem::span(Span::styled(" FREE ", style)));
            footer.push_left(FooterItem::span(Span::styled(" [^] ▲ ▼ ◀ ▶ Move ", style)));
            if !sort_str.is_empty() {
                footer.push_left(tagged_footer("Sort", style, sort_str).into());
            }
        } else {
            let style = Style::default()
                .fg(Color::Blue)
                .add_modifier(Modifier::REVERSED);

            footer.push_left(FooterItem::span(Span::styled(" NORMAL ", style)));
            footer.push_left(FooterItem::span(Span::styled(
                " SPACE / [^] ▲ ▼ ◀ ▶ Move ",
                style,
            )));
            if !sort_str.is_empty() {
                footer.push_left(tagged_footer("Sort", style, sort_str).into());
            }
        }

        let widget = FooterWidget::new(&footer);
        widget.render(area, buf);
    }
}

impl<'a, T, S> Widget for MovableList<'a, T, S>
where
    S: SortMethod<T> + EndlessSelf + Default + ToString,
    T: MovableListItem<'a>,
    MovableListState<'a, T, S>: MovableListManage,
{
    fn render(self, area: tui::layout::Rect, buf: &mut tui::buffer::Buffer) {
        let num = self.state.items.len();

        let offset = self.state.offset;

        let block = if offset.hold {
            get_focused_block(&self.title)
        } else {
            get_block(&self.title)
        };
        let pad = self.state.padding;
        let inner = block.inner(area);
        let inner = if pad == 0 {
            inner
        } else {
            Rect {
                x: inner.x + pad,
                y: inner.y,
                width: inner.width.saturating_sub(pad * 2),
                height: inner.height,
            }
        };

        let height = inner.height as usize;

        // Calculate which portion of the list will be displayed
        let y_offset = if offset.y + 1 > num {
            num.saturating_sub(1)
        } else {
            offset.y
        };

        let x_offset = offset.x;

        let index_width = num.to_string().len();
        let index_style = Style::default().fg(Color::DarkGray);

        let x_range = x_offset
            ..(x_offset
                .saturating_add(inner.width as usize)
                .saturating_sub(index_width));
        let with_index = self.state.with_index;
        let rev_index = self.state.reverse_index;

        // Get that portion of items
        let items = if num != 0 {
            self.state
                .items
                .iter()
                .rev()
                .skip(y_offset)
                .take(height as usize)
                .enumerate()
                .map(|(i, x)| {
                    let content = x.to_spans();
                    let x_width = content.width();
                    let content = spans_window_owned(content, &x_range);

                    let mut spans = if x_width != 0 && content.width() == 0 {
                        Span::raw("◀").into()
                    } else {
                        content
                    };

                    if with_index {
                        let cur_index = if rev_index {
                            num - i - y_offset
                        } else {
                            i + y_offset + 1
                        };
                        spans.0.insert(
                            0,
                            Span::styled(
                                format!("{:>width$} ", cur_index, width = index_width),
                                index_style,
                            ),
                        );
                    };
                    ListItem::new(spans)
                })
                .collect::<Vec<_>>()
        } else {
            vec![ListItem::new(Span::raw(
                self.state
                    .placeholder
                    .to_owned()
                    .unwrap_or_else(|| "Nothing's here yet".into()),
            ))]
        };

        block.render(area, buf);
        List::new(items).style(get_text_style()).render(inner, buf);

        self.render_footer(area, buf);
    }
}

// #[test]
// fn test_movable_list() {
//     let items = &["Test1", "测试1", "[ABCD] 🇺🇲 测试 符号 106"].into_iter().map(|x| x.);
//     assert_eq!()
// }

b0ea165effb988535f09e31400423f6a524188bf

@github-actions github-actions bot added the todo label Nov 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

0 participants