mirror of
				https://github.com/ratatui/ratatui.git
				synced 2025-11-04 07:25:11 +00:00 
			
		
		
		
	In a recent commit we added Rec::split, but this feels more ergonomic as Layout::areas. This also adds Layout::spacers to get the spacers between the areas.
		
			
				
	
	
		
			153 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			153 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use itertools::Itertools;
 | 
						|
use ratatui::{prelude::*, widgets::*};
 | 
						|
use unicode_width::UnicodeWidthStr;
 | 
						|
 | 
						|
use crate::{RgbSwatch, THEME};
 | 
						|
 | 
						|
#[derive(Debug, Default)]
 | 
						|
pub struct Email {
 | 
						|
    from: &'static str,
 | 
						|
    subject: &'static str,
 | 
						|
    body: &'static str,
 | 
						|
}
 | 
						|
 | 
						|
const EMAILS: &[Email] = &[
 | 
						|
    Email {
 | 
						|
        from: "Alice <alice@example.com>",
 | 
						|
        subject: "Hello",
 | 
						|
        body: "Hi Bob,\nHow are you?\n\nAlice",
 | 
						|
    },
 | 
						|
    Email {
 | 
						|
        from: "Bob <bob@example.com>",
 | 
						|
        subject: "Re: Hello",
 | 
						|
        body: "Hi Alice,\nI'm fine, thanks!\n\nBob",
 | 
						|
    },
 | 
						|
    Email {
 | 
						|
        from: "Charlie <charlie@example.com>",
 | 
						|
        subject: "Re: Hello",
 | 
						|
        body: "Hi Alice,\nI'm fine, thanks!\n\nCharlie",
 | 
						|
    },
 | 
						|
    Email {
 | 
						|
        from: "Dave <dave@example.com>",
 | 
						|
        subject: "Re: Hello (STOP REPLYING TO ALL)",
 | 
						|
        body: "Hi Everyone,\nPlease stop replying to all.\n\nDave",
 | 
						|
    },
 | 
						|
    Email {
 | 
						|
        from: "Eve <eve@example.com>",
 | 
						|
        subject: "Re: Hello (STOP REPLYING TO ALL)",
 | 
						|
        body: "Hi Everyone,\nI'm reading all your emails.\n\nEve",
 | 
						|
    },
 | 
						|
];
 | 
						|
 | 
						|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
 | 
						|
pub struct EmailTab {
 | 
						|
    row_index: usize,
 | 
						|
}
 | 
						|
 | 
						|
impl EmailTab {
 | 
						|
    /// Select the previous email (with wrap around).
 | 
						|
    pub fn prev(&mut self) {
 | 
						|
        self.row_index = self.row_index.saturating_add(EMAILS.len() - 1) % EMAILS.len();
 | 
						|
    }
 | 
						|
 | 
						|
    /// Select the next email (with wrap around).
 | 
						|
    pub fn next(&mut self) {
 | 
						|
        self.row_index = self.row_index.saturating_add(1) % EMAILS.len();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
impl Widget for EmailTab {
 | 
						|
    fn render(self, area: Rect, buf: &mut Buffer) {
 | 
						|
        RgbSwatch.render(area, buf);
 | 
						|
        let area = area.inner(&Margin {
 | 
						|
            vertical: 1,
 | 
						|
            horizontal: 2,
 | 
						|
        });
 | 
						|
        Clear.render(area, buf);
 | 
						|
        let vertical = Layout::vertical([Constraint::Length(5), Constraint::Min(0)]);
 | 
						|
        let [inbox, email] = vertical.areas(area);
 | 
						|
        render_inbox(self.row_index, inbox, buf);
 | 
						|
        render_email(self.row_index, email, buf);
 | 
						|
    }
 | 
						|
}
 | 
						|
fn render_inbox(selected_index: usize, area: Rect, buf: &mut Buffer) {
 | 
						|
    let vertical = Layout::vertical([Constraint::Length(1), Constraint::Min(0)]);
 | 
						|
    let [tabs, inbox] = vertical.areas(area);
 | 
						|
    let theme = THEME.email;
 | 
						|
    Tabs::new(vec![" Inbox ", " Sent ", " Drafts "])
 | 
						|
        .style(theme.tabs)
 | 
						|
        .highlight_style(theme.tabs_selected)
 | 
						|
        .select(0)
 | 
						|
        .divider("")
 | 
						|
        .render(tabs, buf);
 | 
						|
 | 
						|
    let highlight_symbol = ">>";
 | 
						|
    let from_width = EMAILS
 | 
						|
        .iter()
 | 
						|
        .map(|e| e.from.width())
 | 
						|
        .max()
 | 
						|
        .unwrap_or_default();
 | 
						|
    let items = EMAILS
 | 
						|
        .iter()
 | 
						|
        .map(|e| {
 | 
						|
            let from = format!("{:width$}", e.from, width = from_width).into();
 | 
						|
            ListItem::new(Line::from(vec![from, " ".into(), e.subject.into()]))
 | 
						|
        })
 | 
						|
        .collect_vec();
 | 
						|
    let mut state = ListState::default().with_selected(Some(selected_index));
 | 
						|
    StatefulWidget::render(
 | 
						|
        List::new(items)
 | 
						|
            .style(theme.inbox)
 | 
						|
            .highlight_style(theme.selected_item)
 | 
						|
            .highlight_symbol(highlight_symbol),
 | 
						|
        inbox,
 | 
						|
        buf,
 | 
						|
        &mut state,
 | 
						|
    );
 | 
						|
    let mut scrollbar_state = ScrollbarState::default()
 | 
						|
        .content_length(EMAILS.len())
 | 
						|
        .position(selected_index);
 | 
						|
    Scrollbar::default()
 | 
						|
        .begin_symbol(None)
 | 
						|
        .end_symbol(None)
 | 
						|
        .track_symbol(None)
 | 
						|
        .thumb_symbol("▐")
 | 
						|
        .render(inbox, buf, &mut scrollbar_state);
 | 
						|
}
 | 
						|
 | 
						|
fn render_email(selected_index: usize, area: Rect, buf: &mut Buffer) {
 | 
						|
    let theme = THEME.email;
 | 
						|
    let email = EMAILS.get(selected_index);
 | 
						|
    let block = Block::new()
 | 
						|
        .style(theme.body)
 | 
						|
        .padding(Padding::new(2, 2, 0, 0))
 | 
						|
        .borders(Borders::TOP)
 | 
						|
        .border_type(BorderType::Thick);
 | 
						|
    let inner = block.inner(area);
 | 
						|
    block.render(area, buf);
 | 
						|
    if let Some(email) = email {
 | 
						|
        let vertical = Layout::vertical([Constraint::Length(3), Constraint::Min(0)]);
 | 
						|
        let [headers_area, body_area] = vertical.areas(inner);
 | 
						|
        let headers = vec![
 | 
						|
            Line::from(vec![
 | 
						|
                "From: ".set_style(theme.header),
 | 
						|
                email.from.set_style(theme.header_value),
 | 
						|
            ]),
 | 
						|
            Line::from(vec![
 | 
						|
                "Subject: ".set_style(theme.header),
 | 
						|
                email.subject.set_style(theme.header_value),
 | 
						|
            ]),
 | 
						|
            "-".repeat(inner.width as usize).dim().into(),
 | 
						|
        ];
 | 
						|
        Paragraph::new(headers)
 | 
						|
            .style(theme.body)
 | 
						|
            .render(headers_area, buf);
 | 
						|
        let body = email.body.lines().map(Line::from).collect_vec();
 | 
						|
        Paragraph::new(body)
 | 
						|
            .style(theme.body)
 | 
						|
            .render(body_area, buf);
 | 
						|
    } else {
 | 
						|
        Paragraph::new("No email selected").render(inner, buf);
 | 
						|
    }
 | 
						|
}
 |