// This file is part of Deja-vu. // // Deja-vu is free software: you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Deja-vu is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Deja-vu. If not, see . // use crate::app::App; use ratatui::{ layout::{Constraint, Direction, Layout, Rect}, style::{Color, Style, Stylize}, text::{Line, Span}, widgets::{Block, Borders, Clear, HighlightSpacing, List, ListItem, Padding, Paragraph, Wrap}, Frame, }; /// Renders the user interface widgets. pub fn render(app: &mut App, frame: &mut Frame) { let chunks = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Length(3), Constraint::Min(1), Constraint::Length(3), ]) .split(frame.size()); // Title Block let title_text = Span::styled("Deja-vu: PE Menu", Style::default().fg(Color::LightCyan)); let title = Paragraph::new(Line::from(title_text).centered()) .block(Block::default().borders(Borders::ALL)); frame.render_widget(title, chunks[0]); // Main Block let main_chunk = centered_rect(65, 90, chunks[1]); render_main_pane(frame, app, main_chunk); // Bottom Block let footer_text = Span::styled( "(Enter) to select / (p) to poweroff / (r) to restart / (t) for terminal", Style::default().fg(Color::DarkGray), ); let footer = Paragraph::new(Line::from(footer_text).centered()) .block(Block::default().borders(Borders::ALL)); frame.render_widget(footer, chunks[2]); // Popup blocks if app.popup.is_some() { render_popup_pane(frame, app); } } fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { // Cut the given rectangle into three vertical pieces let popup_layout = Layout::default() .direction(Direction::Vertical) .constraints([ Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage(percent_y), Constraint::Percentage((100 - percent_y) / 2), ]) .split(r); // Then cut the middle vertical piece into three width-wise pieces Layout::default() .direction(Direction::Horizontal) .constraints([ Constraint::Percentage((100 - percent_x) / 2), Constraint::Percentage(percent_x), Constraint::Percentage((100 - percent_x) / 2), ]) .split(popup_layout[1])[1] // Return the middle chunk } fn render_main_pane(frame: &mut Frame, app: &mut App, chunk: Rect) { let mut list_items = Vec::::new(); for entry in &app.main_menu.items { let text = if entry.separator { if entry.name.is_empty() { String::from("....................") } else { entry.name.clone() } } else { entry.name.clone() }; list_items.push(ListItem::new(format!(" {text}\n\n\n"))); } let list = List::new(list_items) .block( Block::default() .borders(Borders::ALL) .padding(Padding::new(20, 20, 5, 5)), ) .highlight_spacing(HighlightSpacing::Always) .highlight_style(Style::new().green().bold()) .highlight_symbol(" --> ") .repeat_highlight_symbol(false); frame.render_stateful_widget(list, chunk, &mut app.main_menu.state); } fn render_popup_pane(frame: &mut Frame, app: &mut App) { let popup_block = Block::default() .borders(Borders::ALL) .style(Style::default().red().bold()); if let Some(popup) = &app.popup { let scan_paragraph = Paragraph::new(vec![ Line::from(Span::raw(&popup.title)), Line::default(), Line::from(Span::raw(&popup.body)), ]) .block(popup_block) .centered() .wrap(Wrap { trim: false }); let area = centered_rect(60, 25, frame.size()); frame.render_widget(Clear, area); frame.render_widget(scan_paragraph, area); } }