diff options
| author | lukeflo | 2024-10-24 18:04:47 +0200 |
|---|---|---|
| committer | lukeflo | 2024-10-24 18:04:47 +0200 |
| commit | 9de21c23fac3833fba2dab9adeb0cfe04cf44c53 (patch) | |
| tree | dec013eca86878980d17b98b33063e03331b6ab9 /src/tui/ui.rs | |
| parent | c01722b7b517ea5fbee942276ca9f6442cae068b (diff) | |
| download | bibiman-9de21c23fac3833fba2dab9adeb0cfe04cf44c53.tar.gz bibiman-9de21c23fac3833fba2dab9adeb0cfe04cf44c53.zip | |
rewrote ui.rs
Diffstat (limited to 'src/tui/ui.rs')
| -rw-r--r-- | src/tui/ui.rs | 1709 |
1 files changed, 1183 insertions, 526 deletions
diff --git a/src/tui/ui.rs b/src/tui/ui.rs index d5571c8..da9a7d5 100644 --- a/src/tui/ui.rs +++ b/src/tui/ui.rs @@ -17,19 +17,22 @@ use crate::bibiman::entries::EntryTableColumn; use crate::bibiman::keywords::TagListItem; -use crate::bibiman::{Bibiman, CurrentArea, FormerArea}; +use crate::bibiman::{CurrentArea, FormerArea}; use crate::App; +use ratatui::layout::{Direction, Position}; +use ratatui::widgets::Clear; +use ratatui::Frame; use ratatui::{ - buffer::Buffer, layout::{Alignment, Constraint, Layout, Rect}, style::{Color, Modifier, Style, Stylize}, symbols, text::{Line, Span, Text}, widgets::{ Block, Borders, Cell, HighlightSpacing, List, ListItem, Padding, Paragraph, Row, Scrollbar, - ScrollbarOrientation, StatefulWidget, Table, Widget, Wrap, + ScrollbarOrientation, Table, Wrap, }, }; +use tui_input::Input; const MAIN_BLUE_COLOR: Color = Color::Indexed(39); // const MAIN_PURPLE_COLOR: Color = Color::Indexed(129); @@ -68,570 +71,1224 @@ impl From<&TagListItem> for ListItem<'_> { } } -impl Widget for &mut App { - fn render(self, area: Rect, buf: &mut Buffer) { - let [header_area, main_area, footer_area] = Layout::vertical([ +pub fn render_ui(app: &mut App, frame: &mut Frame) { + let [header_area, main_area, footer_area] = Layout::new( + Direction::Vertical, + [ Constraint::Length(1), Constraint::Fill(1), Constraint::Length(3), - ]) - .areas(area); - - let [list_area, item_area] = - Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]).areas(main_area); - - let [entry_area, entry_info_area] = - Layout::vertical([Constraint::Fill(1), Constraint::Length(2)]).areas(list_area); - - let [tag_area, info_area] = - Layout::horizontal([Constraint::Max(25), Constraint::Min(35)]).areas(item_area); - - // Render header and footer - Bibiman::render_header(header_area, buf); - self.bibiman.render_footer(footer_area, buf); - // Render list area where entry gets selected - self.bibiman.render_entrytable(entry_area, buf); - self.bibiman.render_file_info(entry_info_area, buf); - // Render infos related to selected entry - self.bibiman.render_taglist(tag_area, buf); - self.bibiman.render_selected_item(info_area, buf); - } + ], + ) + .direction(Direction::Vertical) + .areas(frame.area()); + // .split(frame.area()); + + let [list_area, item_area] = + Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]).areas(main_area); + + let [entry_area, entry_info_area] = + Layout::vertical([Constraint::Fill(1), Constraint::Length(2)]).areas(list_area); + + let [tag_area, info_area] = + Layout::horizontal([Constraint::Max(25), Constraint::Min(35)]).areas(item_area); + + render_header(frame, header_area); + render_footer(app, frame, footer_area); + render_file_info(app, frame, entry_info_area); + render_entrytable(app, frame, entry_area); + render_selected_item(app, frame, info_area); + render_taglist(app, frame, tag_area); + // Bibiman::render_header(header_area, buf); + // self.bibiman.render_footer(footer_area, buf); + // Render list area where entry gets selected + // self.bibiman.render_entrytable(entry_area, buf); + // self.bibiman.render_file_info(entry_info_area, buf); + // Render infos related to selected entry + // self.bibiman.render_taglist(tag_area, buf); + // self.bibiman.render_selected_item(info_area, buf); } -impl Bibiman { - pub fn render_header(area: Rect, buf: &mut Buffer) { - Paragraph::new("BIBIMAN – BibLaTeX manager TUI") - .bold() - .fg(MAIN_BLUE_COLOR) - .centered() - .render(area, buf); - } +pub fn render_header(frame: &mut Frame, rect: Rect) { + let main_header = Paragraph::new("BIBIMAN – BibLaTeX manager TUI") + .bold() + .fg(MAIN_BLUE_COLOR) + .centered(); + frame.render_widget(main_header, rect) +} - pub fn render_footer(&mut self, area: Rect, buf: &mut Buffer) { - match &self.current_area { - CurrentArea::SearchArea => { - let search_title = { - match self.former_area { - Some(FormerArea::EntryArea) => { - let search_title = " Search Entries ".to_string(); - search_title - } - Some(FormerArea::TagArea) => { - let search_title = " Search Keywords ".to_string(); - search_title - } - _ => { - let search_title = " Search ".to_string(); - search_title - } +pub fn render_footer(app: &mut App, frame: &mut Frame, rect: Rect) { + match &app.bibiman.current_area { + CurrentArea::SearchArea => { + let search_title = { + match app.bibiman.former_area { + Some(FormerArea::EntryArea) => { + let search_title = " Search Entries ".to_string(); + search_title } - }; - - let block = Block::bordered() - .title(Line::styled(search_title, BOX_SELECTED_TITLE_STYLE)) - .border_style(BOX_SELECTED_BOX_STYLE) - .border_set(symbols::border::THICK); - Paragraph::new(self.search_struct.search_string.clone()) - .block(block) - .render(area, buf); - } - _ => { - let style_emph = Style::new().bold().fg(TEXT_FG_COLOR); - let block = Block::bordered() - .title(Line::raw(" Basic Commands ").centered()) - .border_style(BOX_UNSELECTED_BORDER_STYLE) - .border_set(symbols::border::PLAIN); - Paragraph::new(Line::from(vec![ - Span::styled("j/k: ", style_emph), - Span::raw("move | "), - Span::styled("g/G: ", style_emph), - Span::raw("top/bottom | "), - Span::styled("TAB: ", style_emph), - Span::raw("switch tab | "), - Span::styled("y: ", style_emph), - Span::raw("yank citekey | "), - Span::styled("e: ", style_emph), - Span::raw("edit | "), - Span::styled("/: ", style_emph), - Span::raw("search | "), - Span::styled("o/u: ", style_emph), - Span::raw("open PDF/DOI"), - ])) - .block(block) - .centered() - .render(area, buf); - } + Some(FormerArea::TagArea) => { + let search_title = " Search Keywords ".to_string(); + search_title + } + _ => { + let search_title = " Search ".to_string(); + search_title + } + } + }; + + let block = Block::bordered() + .title(Line::styled(search_title, BOX_SELECTED_TITLE_STYLE)) + .border_style(BOX_SELECTED_BOX_STYLE) + .border_set(symbols::border::THICK); + frame.render_widget( + Paragraph::new(app.bibiman.search_struct.search_string.clone()).block(block), + rect, + ); + render_cursor(app, frame, rect); + } + _ => { + let style_emph = Style::new().bold().fg(TEXT_FG_COLOR); + let block = Block::bordered() + .title(Line::raw(" Basic Commands ").centered()) + .border_style(BOX_UNSELECTED_BORDER_STYLE) + .border_set(symbols::border::PLAIN); + let keybindigns = Paragraph::new(Line::from(vec![ + Span::styled("j/k: ", style_emph), + Span::raw("move | "), + Span::styled("g/G: ", style_emph), + Span::raw("top/bottom | "), + Span::styled("TAB: ", style_emph), + Span::raw("switch tab | "), + Span::styled("y: ", style_emph), + Span::raw("yank citekey | "), + Span::styled("e: ", style_emph), + Span::raw("edit | "), + Span::styled("/: ", style_emph), + Span::raw("search | "), + Span::styled("o/u: ", style_emph), + Span::raw("open PDF/DOI"), + ])) + .block(block) + .centered(); + frame.render_widget(keybindigns, rect); } } +} - // Render info of the current file and process - // 1. Basename of the currently loaded file - // 2. Keyword by which the entries are filtered at the moment - // 3. Currently selected entry and total count of entries - pub fn render_file_info(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::new() // can also be Block::new - // Leave Top empty to simulate one large box with borders of entry list - .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) - .border_set(if let CurrentArea::EntryArea = self.current_area { - symbols::border::THICK - } else { - symbols::border::PLAIN - }) - .border_style(if let CurrentArea::EntryArea = self.current_area { - BOX_SELECTED_BOX_STYLE - } else { - BOX_UNSELECTED_BORDER_STYLE - }); - - let [file_area, keyword_area, count_area] = Layout::horizontal([ - Constraint::Fill(3), - Constraint::Fill(4), - Constraint::Fill(1), - ]) - .horizontal_margin(1) - .areas(area); - - Line::from(vec![ - Span::raw("File: ").bold(), - Span::raw(self.main_bibfile.file_name().unwrap().to_string_lossy()).bold(), - ]) - .bg(HEADER_FOOTER_BG) - .render(file_area, buf); - - Line::from(if !self.tag_list.selected_keywords.is_empty() { - vec![ - Span::raw("Selected keywords: "), - // Show all keywords in correct order if list is filtered - // successively by multiple keywords - Span::raw(self.tag_list.selected_keywords.join(" → ")) - .bold() - .green(), - ] +// Render info of the current file and process +// 1. Basename of the currently loaded file +// 2. Keyword by which the entries are filtered at the moment +// 3. Currently selected entry and total count of entries +pub fn render_file_info(app: &mut App, frame: &mut Frame, rect: Rect) { + let block = Block::new() // can also be Block::new + // Leave Top empty to simulate one large box with borders of entry list + .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) + .border_set(if let CurrentArea::EntryArea = app.bibiman.current_area { + symbols::border::THICK } else { - vec![Span::raw(" ")] + symbols::border::PLAIN }) - .bg(HEADER_FOOTER_BG) - .render(keyword_area, buf); + .border_style(if let CurrentArea::EntryArea = app.bibiman.current_area { + BOX_SELECTED_BOX_STYLE + } else { + BOX_UNSELECTED_BORDER_STYLE + }); - Line::from(if self.entry_table.entry_table_state.selected().is_some() { + let [file_area, keyword_area, count_area] = Layout::horizontal([ + Constraint::Fill(3), + Constraint::Fill(4), + Constraint::Fill(1), + ]) + .horizontal_margin(1) + .areas(rect); + + let file_info = Line::from(vec![ + Span::raw("File: ").bold(), + Span::raw( + app.bibiman + .main_bibfile + .file_name() + .unwrap() + .to_string_lossy(), + ) + .bold(), + ]) + .bg(HEADER_FOOTER_BG); + // .render(file_area, buf); + + let cur_keywords = Line::from(if !app.bibiman.tag_list.selected_keywords.is_empty() { + vec![ + Span::raw("Selected keywords: "), + // Show all keywords in correct order if list is filtered + // successively by multiple keywords + Span::raw(app.bibiman.tag_list.selected_keywords.join(" → ")) + .bold() + .green(), + ] + } else { + vec![Span::raw(" ")] + }) + .bg(HEADER_FOOTER_BG); + // .render(keyword_area, buf); + + let item_count = Line::from( + if app + .bibiman + .entry_table + .entry_table_state + .selected() + .is_some() + { vec![ - Span::raw((self.entry_table.entry_table_state.selected().unwrap() + 1).to_string()) - .bold(), + Span::raw( + (app.bibiman + .entry_table + .entry_table_state + .selected() + .unwrap() + + 1) + .to_string(), + ) + .bold(), Span::raw("/"), - Span::raw(self.entry_table.entry_table_items.len().to_string()), + Span::raw(app.bibiman.entry_table.entry_table_items.len().to_string()), ] } else { vec![Span::raw("No entries")] + }, + ) + .right_aligned() + .bg(HEADER_FOOTER_BG); + // .render(count_area, buf); + frame.render_widget(file_info, file_area); + frame.render_widget(cur_keywords, keyword_area); + frame.render_widget(item_count, count_area); +} + +pub fn render_entrytable(app: &mut App, frame: &mut Frame, rect: Rect) { + let block = Block::new() // can also be Block::new + .title( + Line::styled( + " Bibliographic Entries ", + if let CurrentArea::EntryArea = app.bibiman.current_area { + BOX_SELECTED_TITLE_STYLE + } else { + BOX_UNSELECTED_TITLE_STYLE + }, + ) + .centered(), + ) + .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) + .border_set(if let CurrentArea::EntryArea = app.bibiman.current_area { + symbols::border::THICK + } else { + symbols::border::PLAIN }) - .right_aligned() - .bg(HEADER_FOOTER_BG) - .render(count_area, buf); + .border_style(if let CurrentArea::EntryArea = app.bibiman.current_area { + BOX_SELECTED_BOX_STYLE + } else { + BOX_UNSELECTED_BORDER_STYLE + }); - // Render that stuff - Widget::render(block, area, buf) - } + let header_style = Style::default() + .bold() + .fg(TEXT_FG_COLOR) + .bg(HEADER_FOOTER_BG); - pub fn render_entrytable(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::new() // can also be Block::new - .title( - Line::styled( - " Bibliographic Entries ", - if let CurrentArea::EntryArea = self.current_area { - BOX_SELECTED_TITLE_STYLE - } else { - BOX_UNSELECTED_TITLE_STYLE - }, - ) - .centered(), - ) - .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) - .border_set(if let CurrentArea::EntryArea = self.current_area { - symbols::border::THICK - } else { - symbols::border::PLAIN - }) - .border_style(if let CurrentArea::EntryArea = self.current_area { - BOX_SELECTED_BOX_STYLE - } else { - BOX_UNSELECTED_BORDER_STYLE - }); - - let header_style = Style::default() - .bold() - .fg(TEXT_FG_COLOR) - .bg(HEADER_FOOTER_BG); - - let header_selected_col = Style::default().underlined(); - - let header = Row::new(vec![ - Cell::from(Line::from(vec![ - { - if let EntryTableColumn::Authors = self.entry_table.entry_table_selected_column - { - Span::styled("Author", header_selected_col) - } else { - Span::raw("Author") - } - }, - { - if let EntryTableColumn::Authors = self.entry_table.entry_table_sorted_by_col { - Span::raw(format!( - " {}", - if self.entry_table.entry_table_reversed_sort { - SORTED_ENTRIES_REVERSED - } else { - SORTED_ENTRIES - } - )) - } else { - Span::raw("") - } - }, - ])), - Cell::from(Line::from(vec![ + let header_selected_col = Style::default().underlined(); + + let header = Row::new(vec![ + Cell::from(Line::from(vec![ + { + if let EntryTableColumn::Authors = + app.bibiman.entry_table.entry_table_selected_column { - if let EntryTableColumn::Title = self.entry_table.entry_table_selected_column { - Span::styled("Title", header_selected_col) - } else { - Span::raw("Title") - } - }, + Span::styled("Author", header_selected_col) + } else { + Span::raw("Author") + } + }, + { + if let EntryTableColumn::Authors = app.bibiman.entry_table.entry_table_sorted_by_col { - if let EntryTableColumn::Title = self.entry_table.entry_table_sorted_by_col { - Span::raw(format!( - " {}", - if self.entry_table.entry_table_reversed_sort { - SORTED_ENTRIES_REVERSED - } else { - SORTED_ENTRIES - } - )) - } else { - Span::raw("") - } - }, - ])), - Cell::from(Line::from(vec![ + Span::raw(format!( + " {}", + if app.bibiman.entry_table.entry_table_reversed_sort { + SORTED_ENTRIES_REVERSED + } else { + SORTED_ENTRIES + } + )) + } else { + Span::raw("") + } + }, + ])), + Cell::from(Line::from(vec![ + { + if let EntryTableColumn::Title = app.bibiman.entry_table.entry_table_selected_column { - if let EntryTableColumn::Year = self.entry_table.entry_table_selected_column { - Span::styled("Year", header_selected_col) - } else { - Span::raw("Year") - } - }, + Span::styled("Title", header_selected_col) + } else { + Span::raw("Title") + } + }, + { + if let EntryTableColumn::Title = app.bibiman.entry_table.entry_table_sorted_by_col { + Span::raw(format!( + " {}", + if app.bibiman.entry_table.entry_table_reversed_sort { + SORTED_ENTRIES_REVERSED + } else { + SORTED_ENTRIES + } + )) + } else { + Span::raw("") + } + }, + ])), + Cell::from(Line::from(vec![ + { + if let EntryTableColumn::Year = app.bibiman.entry_table.entry_table_selected_column { - if let EntryTableColumn::Year = self.entry_table.entry_table_sorted_by_col { - Span::raw(format!( - " {}", - if self.entry_table.entry_table_reversed_sort { - SORTED_ENTRIES_REVERSED - } else { - SORTED_ENTRIES - } - )) - } else { - Span::raw("") - } - }, - ])), - Cell::from(Line::from(vec![ + Span::styled("Year", header_selected_col) + } else { + Span::raw("Year") + } + }, + { + if let EntryTableColumn::Year = app.bibiman.entry_table.entry_table_sorted_by_col { + Span::raw(format!( + " {}", + if app.bibiman.entry_table.entry_table_reversed_sort { + SORTED_ENTRIES_REVERSED + } else { + SORTED_ENTRIES + } + )) + } else { + Span::raw("") + } + }, + ])), + Cell::from(Line::from(vec![ + { + if let EntryTableColumn::Pubtype = + app.bibiman.entry_table.entry_table_selected_column { - if let EntryTableColumn::Pubtype = self.entry_table.entry_table_selected_column - { - Span::styled("Pubtype", header_selected_col) - } else { - Span::raw("Pubtype") - } - }, + Span::styled("Pubtype", header_selected_col) + } else { + Span::raw("Pubtype") + } + }, + { + if let EntryTableColumn::Pubtype = app.bibiman.entry_table.entry_table_sorted_by_col { - if let EntryTableColumn::Pubtype = self.entry_table.entry_table_sorted_by_col { - Span::raw(format!( - " {}", - if self.entry_table.entry_table_reversed_sort { - SORTED_ENTRIES_REVERSED - } else { - SORTED_ENTRIES - } - )) - } else { - Span::raw("") - } + Span::raw(format!( + " {}", + if app.bibiman.entry_table.entry_table_reversed_sort { + SORTED_ENTRIES_REVERSED + } else { + SORTED_ENTRIES + } + )) + } else { + Span::raw("") + } + }, + ])), + ]) + .style(header_style) + .height(1); + + // Iterate over vector storing each entries data fields + let rows = app + .bibiman + .entry_table + .entry_table_items + .iter_mut() + .enumerate() + .map(|(_i, data)| { + let item = data.ref_vec(); + item.into_iter() + .map(|content| Cell::from(Text::from(format!("{content}")))) + .collect::<Row>() + .style(Style::new().fg(TEXT_FG_COLOR)) //.bg(alternate_colors(i))) + .height(1) + }); + let entry_table = Table::new( + rows, + [ + Constraint::Percentage(20), + Constraint::Fill(1), + Constraint::Length( + if let EntryTableColumn::Year = app.bibiman.entry_table.entry_table_sorted_by_col { + 6 + } else { + 4 }, - ])), - ]) - .style(header_style) - .height(1); + ), + Constraint::Percentage(10), + ], + ) + .block(block) + .header(header) + .column_spacing(2) + .row_highlight_style(SELECTED_STYLE) + // .bg(Color::Black) + .highlight_spacing(HighlightSpacing::Always); - // Iterate over vector storing each entries data fields - let rows = self - .entry_table - .entry_table_items - .iter_mut() - .enumerate() - .map(|(_i, data)| { - let item = data.ref_vec(); - item.into_iter() - .map(|content| Cell::from(Text::from(format!("{content}")))) - .collect::<Row>() - .style(Style::new().fg(TEXT_FG_COLOR)) //.bg(alternate_colors(i))) - .height(1) - }); - let entry_table = Table::new( - rows, - [ - Constraint::Percentage(20), - Constraint::Fill(1), - Constraint::Length( - if let EntryTableColumn::Year = self.entry_table.entry_table_sorted_by_col { - 6 - } else { - 4 - }, - ), - Constraint::Percentage(10), - ], - ) - .block(block) - .header(header) - .column_spacing(2) - .row_highlight_style(SELECTED_STYLE) - // .bg(Color::Black) - .highlight_spacing(HighlightSpacing::Always); - StatefulWidget::render( - entry_table, - area, - buf, - &mut self.entry_table.entry_table_state, - ); + frame.render_stateful_widget( + entry_table, + rect, + &mut app.bibiman.entry_table.entry_table_state, + ); - // Scrollbar for entry table - let scrollbar = Scrollbar::default() - .orientation(ScrollbarOrientation::VerticalRight) - .track_symbol(None) - .begin_symbol(SCROLLBAR_UPPER_CORNER) - .end_symbol(None) - .thumb_style(Style::new().fg(Color::DarkGray)); + // Scrollbar for entry table + let scrollbar = Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .track_symbol(None) + .begin_symbol(SCROLLBAR_UPPER_CORNER) + .end_symbol(None) + .thumb_style(Style::new().fg(Color::DarkGray)); - if let CurrentArea::EntryArea = self.current_area { - // render the scrollbar - StatefulWidget::render( - scrollbar, - area, - buf, - &mut self.entry_table.entry_scroll_state, - ); - } + if let CurrentArea::EntryArea = app.bibiman.current_area { + // render the scrollbar + frame.render_stateful_widget( + scrollbar, + rect, + &mut app.bibiman.entry_table.entry_scroll_state, + ); } +} - pub fn render_selected_item(&mut self, area: Rect, buf: &mut Buffer) { - // We get the info depending on the item's state. - let style_value = Style::new().bold().fg(TEXT_FG_COLOR); - let style_value_sec = Style::new() - .add_modifier(Modifier::ITALIC) - .fg(TEXT_FG_COLOR); - let lines = { - // if self.entry_table.entry_table_items.len() > 0 { - if self.entry_table.entry_table_state.selected().is_some() { - let idx = self.entry_table.entry_table_state.selected().unwrap(); - let cur_entry = &self.entry_table.entry_table_items[idx]; - let mut lines = vec![]; - lines.push(Line::from(vec![ - Span::styled("Authors: ", style_value), - // Span::styled(cur_entry.authors.clone(), Style::new().green()), - Span::styled(cur_entry.authors(), Style::new().green()), - ])); - lines.push(Line::from(vec![ - Span::styled("Title: ", style_value), - Span::styled(cur_entry.title(), Style::new().magenta()), - ])); - lines.push(Line::from(vec![ - Span::styled("Year: ", style_value), - Span::styled(cur_entry.year(), Style::new().light_magenta()), - ])); - // Render keywords in info box in Markdown code style - if !cur_entry.keywords.is_empty() { - let kw: Vec<&str> = cur_entry - .keywords - .split(",") - .map(|k| k.trim()) - .filter(|k| !k.is_empty()) - .collect(); - let mut content = vec![Span::styled("Keywords: ", style_value)]; - for k in kw { - // Add half block highlighted in bg color to enlarge block - content.push(Span::raw("▐").fg(HEADER_FOOTER_BG)); - content.push(Span::styled( - k, - Style::default().bg(HEADER_FOOTER_BG).fg( - // Highlight selected keyword green - if self.tag_list.selected_keywords.iter().any(|e| e == k) { - Color::Green - } else { - TEXT_FG_COLOR - }, - ), - )); - content.push(Span::raw("▌").fg(HEADER_FOOTER_BG)); - } - lines.push(Line::from(content)) - } - if !cur_entry.doi_url.is_empty() || !cur_entry.filepath.is_empty() { - lines.push(Line::raw("")); - } - if !cur_entry.doi_url.is_empty() { - lines.push(Line::from(vec![ - Span::styled("DOI/URL: ", style_value_sec), - Span::styled( - cur_entry.doi_url(), - Style::default().fg(TEXT_FG_COLOR).underlined(), +pub fn render_selected_item(app: &mut App, frame: &mut Frame, rect: Rect) { + // We get the info depending on the item's state. + let style_value = Style::new().bold().fg(TEXT_FG_COLOR); + let style_value_sec = Style::new() + .add_modifier(Modifier::ITALIC) + .fg(TEXT_FG_COLOR); + let lines = { + // if app.bibiman.entry_table.entry_table_items.len() > 0 { + if app + .bibiman + .entry_table + .entry_table_state + .selected() + .is_some() + { + let idx = app + .bibiman + .entry_table + .entry_table_state + .selected() + .unwrap(); + let cur_entry = &app.bibiman.entry_table.entry_table_items[idx]; + let mut lines = vec![]; + lines.push(Line::from(vec![ + Span::styled("Authors: ", style_value), + // Span::styled(cur_entry.authors.clone(), Style::new().green()), + Span::styled(cur_entry.authors(), Style::new().green()), + ])); + lines.push(Line::from(vec![ + Span::styled("Title: ", style_value), + Span::styled(cur_entry.title(), Style::new().magenta()), + ])); + lines.push(Line::from(vec![ + Span::styled("Year: ", style_value), + Span::styled(cur_entry.year(), Style::new().light_magenta()), + ])); + // Render keywords in info box in Markdown code style + if !cur_entry.keywords.is_empty() { + let kw: Vec<&str> = cur_entry + .keywords + .split(",") + .map(|k| k.trim()) + .filter(|k| !k.is_empty()) + .collect(); + let mut content = vec![Span::styled("Keywords: ", style_value)]; + for k in kw { + // Add half block highlighted in bg color to enlarge block + content.push(Span::raw("▐").fg(HEADER_FOOTER_BG)); + content.push(Span::styled( + k, + Style::default().bg(HEADER_FOOTER_BG).fg( + // Highlight selected keyword green + if app + .bibiman + .tag_list + .selected_keywords + .iter() + .any(|e| e == k) + { + Color::Green + } else { + TEXT_FG_COLOR + }, ), - ])); + )); + content.push(Span::raw("▌").fg(HEADER_FOOTER_BG)); } - if !cur_entry.filepath.is_empty() { - lines.push(Line::from(vec![ - Span::styled("File: ", style_value_sec), - Span::styled(cur_entry.filepath(), Style::default().fg(TEXT_FG_COLOR)), - ])); - } - lines.push(Line::from("")); - lines.push(Line::from(vec![Span::styled( - cur_entry.abstract_text.clone(), - Style::default().fg(TEXT_FG_COLOR), - )])); - lines - } else { - let lines = vec![ - Line::from(" "), - Line::from("No entry selected".bold().into_centered_line().red()), - ]; - lines + lines.push(Line::from(content)) } - }; - let info = Text::from(lines); - - // We show the list item's info under the list in this paragraph - let block = Block::bordered() - .title(Line::raw(" Entry Information ").centered().bold()) - // .borders(Borders::TOP) - .border_set(symbols::border::PLAIN) - .border_style(BOX_UNSELECTED_BORDER_STYLE) - // .bg(Color::Black) - .padding(Padding::horizontal(1)); - - // INFO: '.line_count' method only possible with unstable-rendered-line-info feature -> API might change: https://github.com/ratatui/ratatui/issues/293#ref-pullrequest-2027056434 - let box_height = Paragraph::new(info.clone()) - .block(block.clone()) - .wrap(Wrap { trim: false }) - .line_count(area.width); - // Make sure to allow scroll only if text is larger than the rendered area and stop scrolling when last line is reached - let scroll_height = { - if self.entry_table.entry_info_scroll == 0 { - self.entry_table.entry_info_scroll - } else if area.height > box_height as u16 { - self.entry_table.entry_info_scroll = 0; - self.entry_table.entry_info_scroll - } else if self.entry_table.entry_info_scroll > (box_height as u16 + 2 - area.height) { - self.entry_table.entry_info_scroll = box_height as u16 + 2 - area.height; - self.entry_table.entry_info_scroll - } else { - self.entry_table.entry_info_scroll + if !cur_entry.doi_url.is_empty() || !cur_entry.filepath.is_empty() { + lines.push(Line::raw("")); } - }; - - // We can now render the item info - Paragraph::new(info) - .block( - block - // Render arrows to show that info box has content outside the block - .title_bottom( - Line::from( - if box_height > area.height.into() - && self.entry_table.entry_info_scroll - < box_height as u16 + 2 - area.height - { - " ▼ " - } else { - "" - }, - ) - .alignment(Alignment::Right), - ) - .title_top( - Line::from(if scroll_height > 0 { " ▲ " } else { "" }) - .alignment(Alignment::Right), + if !cur_entry.doi_url.is_empty() { + lines.push(Line::from(vec![ + Span::styled("DOI/URL: ", style_value_sec), + Span::styled( + cur_entry.doi_url(), + Style::default().fg(TEXT_FG_COLOR).underlined(), ), - ) - // .fg(TEXT_FG_COLOR) - .wrap(Wrap { trim: false }) - .scroll((scroll_height, 0)) - .render(area, buf); - } + ])); + } + if !cur_entry.filepath.is_empty() { + lines.push(Line::from(vec![ + Span::styled("File: ", style_value_sec), + Span::styled(cur_entry.filepath(), Style::default().fg(TEXT_FG_COLOR)), + ])); + } + lines.push(Line::from("")); + lines.push(Line::from(vec![Span::styled( + cur_entry.abstract_text.clone(), + Style::default().fg(TEXT_FG_COLOR), + )])); + lines + } else { + let lines = vec![ + Line::from(" "), + Line::from("No entry selected".bold().into_centered_line().red()), + ]; + lines + } + }; + let info = Text::from(lines); + + // We show the list item's info under the list in this paragraph + let block = Block::bordered() + .title(Line::raw(" Entry Information ").centered().bold()) + // .borders(Borders::TOP) + .border_set(symbols::border::PLAIN) + .border_style(BOX_UNSELECTED_BORDER_STYLE) + // .bg(Color::Black) + .padding(Padding::horizontal(1)); + + // INFO: '.line_count' method only possible with unstable-rendered-line-info feature -> API might change: https://github.com/ratatui/ratatui/issues/293#ref-pullrequest-2027056434 + let box_height = Paragraph::new(info.clone()) + .block(block.clone()) + .wrap(Wrap { trim: false }) + .line_count(rect.width); + // Make sure to allow scroll only if text is larger than the rendered area and stop scrolling when last line is reached + let scroll_height = { + if app.bibiman.entry_table.entry_info_scroll == 0 { + app.bibiman.entry_table.entry_info_scroll + } else if rect.height > box_height as u16 { + app.bibiman.entry_table.entry_info_scroll = 0; + app.bibiman.entry_table.entry_info_scroll + } else if app.bibiman.entry_table.entry_info_scroll > (box_height as u16 + 2 - rect.height) + { + app.bibiman.entry_table.entry_info_scroll = box_height as u16 + 2 - rect.height; + app.bibiman.entry_table.entry_info_scroll + } else { + app.bibiman.entry_table.entry_info_scroll + } + }; - pub fn render_taglist(&mut self, area: Rect, buf: &mut Buffer) { - let block = Block::bordered() - .title( - Line::styled( - " Keywords ", - if let CurrentArea::TagArea = self.current_area { - BOX_SELECTED_TITLE_STYLE - } else { - BOX_UNSELECTED_TITLE_STYLE - }, + // We can now render the item info + let item_info = Paragraph::new(info) + .block( + block + // Render arrows to show that info box has content outside the block + .title_bottom( + Line::from( + if box_height > rect.height.into() + && app.bibiman.entry_table.entry_info_scroll + < box_height as u16 + 2 - rect.height + { + " ▼ " + } else { + "" + }, + ) + .alignment(Alignment::Right), ) - .centered(), + .title_top( + Line::from(if scroll_height > 0 { " ▲ " } else { "" }) + .alignment(Alignment::Right), + ), + ) + // .fg(TEXT_FG_COLOR) + .wrap(Wrap { trim: false }) + .scroll((scroll_height, 0)); + + frame.render_widget(item_info, rect); +} + +pub fn render_taglist(app: &mut App, frame: &mut Frame, rect: Rect) { + let block = Block::bordered() + .title( + Line::styled( + " Keywords ", + if let CurrentArea::TagArea = app.bibiman.current_area { + BOX_SELECTED_TITLE_STYLE + } else { + BOX_UNSELECTED_TITLE_STYLE + }, ) - .border_set(if let CurrentArea::TagArea = self.current_area { - symbols::border::THICK - } else { - symbols::border::PLAIN - }) - .border_style(if let CurrentArea::TagArea = self.current_area { - BOX_SELECTED_BOX_STYLE - } else { - BOX_UNSELECTED_BORDER_STYLE - }); - // .bg(Color::Black); - - // Iterate through all elements in the `items` and stylize them. - let items: Vec<ListItem> = self - .tag_list - .tag_list_items - .iter() - .enumerate() - .map(|(_i, todo_item)| { - // let color = alternate_colors(i); - ListItem::from(todo_item.to_owned()) //.bg(color) - }) - .collect(); - - // Create a List from all list items and highlight the currently selected one - let list = List::new(items) - .block(block) - .highlight_style(SELECTED_STYLE) - // .highlight_symbol("> ") - .highlight_spacing(HighlightSpacing::Always); - - // Save list length for calculating scrollbar need - // Add 2 to compmensate lines of the block border - let list_length = list.len() + 2; - - // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the - // same method name `render`. - StatefulWidget::render(list, area, buf, &mut self.tag_list.tag_list_state); - - // Scrollbar for keyword list - let scrollbar = Scrollbar::default() - .orientation(ScrollbarOrientation::VerticalRight) - .track_symbol(None) - .begin_symbol(SCROLLBAR_UPPER_CORNER) - .end_symbol(SCROLLBAR_LOWER_CORNER) - .thumb_style(Style::new().fg(Color::DarkGray)); - - if list_length > area.height.into() { - if let CurrentArea::TagArea = self.current_area { - // render the scrollbar - StatefulWidget::render(scrollbar, area, buf, &mut self.tag_list.tag_scroll_state); - } + .centered(), + ) + .border_set(if let CurrentArea::TagArea = app.bibiman.current_area { + symbols::border::THICK + } else { + symbols::border::PLAIN + }) + .border_style(if let CurrentArea::TagArea = app.bibiman.current_area { + BOX_SELECTED_BOX_STYLE + } else { + BOX_UNSELECTED_BORDER_STYLE + }); + // .bg(Color::Black); + + // Iterate through all elements in the `items` and stylize them. + let items: Vec<ListItem> = app + .bibiman + .tag_list + .tag_list_items + .iter() + .enumerate() + .map(|(_i, todo_item)| { + // let color = alternate_colors(i); + ListItem::from(todo_item.to_owned()) //.bg(color) + }) + .collect(); + + // Create a List from all list items and highlight the currently selected one + let list = List::new(items) + .block(block) + .highlight_style(SELECTED_STYLE) + // .highlight_symbol("> ") + .highlight_spacing(HighlightSpacing::Always); + + // Save list length for calculating scrollbar need + // Add 2 to compmensate lines of the block border + let list_length = list.len() + 2; + + // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the + // same method name `render`. + frame.render_stateful_widget(list, rect, &mut app.bibiman.tag_list.tag_list_state); + // StatefulWidget::render(list, area, buf, &mut app.bibiman.tag_list.tag_list_state); + + // Scrollbar for keyword list + let scrollbar = Scrollbar::default() + .orientation(ScrollbarOrientation::VerticalRight) + .track_symbol(None) + .begin_symbol(SCROLLBAR_UPPER_CORNER) + .end_symbol(SCROLLBAR_LOWER_CORNER) + .thumb_style(Style::new().fg(Color::DarkGray)); + + if list_length > rect.height.into() { + if let CurrentArea::TagArea = app.bibiman.current_area { + // render the scrollbar + frame.render_stateful_widget( + scrollbar, + rect, + &mut app.bibiman.tag_list.tag_scroll_state, + ); } } } + +/// Render the cursor when in InputMode +fn render_cursor(app: &mut App, frame: &mut Frame, area: Rect) { + if app.input_mode { + let (x, y) = ( + area.x + + Input::default() + .with_value(app.input.value().to_string()) + .visual_cursor() as u16 + + 2, + area.bottom().saturating_sub(1), + ); + frame.render_widget( + Clear, + Rect { + x, + y, + width: 1, + height: 1, + }, + ); + frame.set_cursor_position(Position::new(x, y)); + } +} + +// impl Widget for &mut App { +// fn render(self, area: Rect, buf: &mut Buffer) { +// let [header_area, main_area, footer_area] = Layout::vertical([ +// Constraint::Length(1), +// Constraint::Fill(1), +// Constraint::Length(3), +// ]) +// .areas(area); + +// let [list_area, item_area] = +// Layout::vertical([Constraint::Fill(1), Constraint::Fill(1)]).areas(main_area); + +// let [entry_area, entry_info_area] = +// Layout::vertical([Constraint::Fill(1), Constraint::Length(2)]).areas(list_area); + +// let [tag_area, info_area] = +// Layout::horizontal([Constraint::Max(25), Constraint::Min(35)]).areas(item_area); + +// // Render header and footer +// Bibiman::render_header(header_area, buf); +// self.bibiman.render_footer(footer_area, buf); +// // Render list area where entry gets selected +// self.bibiman.render_entrytable(entry_area, buf); +// self.bibiman.render_file_info(entry_info_area, buf); +// // Render infos related to selected entry +// self.bibiman.render_taglist(tag_area, buf); +// self.bibiman.render_selected_item(info_area, buf); +// } +// } + +// impl Bibiman { +// pub fn render_header(area: Rect, buf: &mut Buffer) { +// Paragraph::new("BIBIMAN – BibLaTeX manager TUI") +// .bold() +// .fg(MAIN_BLUE_COLOR) +// .centered() +// .render(area, buf); +// } + +// pub fn render_footer(&mut self, area: Rect, buf: &mut Buffer) { +// match &self.current_area { +// CurrentArea::SearchArea => { +// let search_title = { +// match self.former_area { +// Some(FormerArea::EntryArea) => { +// let search_title = " Search Entries ".to_string(); +// search_title +// } +// Some(FormerArea::TagArea) => { +// let search_title = " Search Keywords ".to_string(); +// search_title +// } +// _ => { +// let search_title = " Search ".to_string(); +// search_title +// } +// } +// }; + +// let block = Block::bordered() +// .title(Line::styled(search_title, BOX_SELECTED_TITLE_STYLE)) +// .border_style(BOX_SELECTED_BOX_STYLE) +// .border_set(symbols::border::THICK); +// Paragraph::new(self.search_struct.search_string.clone()) +// .block(block) +// .render(area, buf); +// } +// _ => { +// let style_emph = Style::new().bold().fg(TEXT_FG_COLOR); +// let block = Block::bordered() +// .title(Line::raw(" Basic Commands ").centered()) +// .border_style(BOX_UNSELECTED_BORDER_STYLE) +// .border_set(symbols::border::PLAIN); +// Paragraph::new(Line::from(vec![ +// Span::styled("j/k: ", style_emph), +// Span::raw("move | "), +// Span::styled("g/G: ", style_emph), +// Span::raw("top/bottom | "), +// Span::styled("TAB: ", style_emph), +// Span::raw("switch tab | "), +// Span::styled("y: ", style_emph), +// Span::raw("yank citekey | "), +// Span::styled("e: ", style_emph), +// Span::raw("edit | "), +// Span::styled("/: ", style_emph), +// Span::raw("search | "), +// Span::styled("o/u: ", style_emph), +// Span::raw("open PDF/DOI"), +// ])) +// .block(block) +// .centered() +// .render(area, buf); +// } +// } +// } + +// // Render info of the current file and process +// // 1. Basename of the currently loaded file +// // 2. Keyword by which the entries are filtered at the moment +// // 3. Currently selected entry and total count of entries +// pub fn render_file_info(&mut self, area: Rect, buf: &mut Buffer) { +// let block = Block::new() // can also be Block::new +// // Leave Top empty to simulate one large box with borders of entry list +// .borders(Borders::LEFT | Borders::RIGHT | Borders::BOTTOM) +// .border_set(if let CurrentArea::EntryArea = self.current_area { +// symbols::border::THICK +// } else { +// symbols::border::PLAIN +// }) +// .border_style(if let CurrentArea::EntryArea = self.current_area { +// BOX_SELECTED_BOX_STYLE +// } else { +// BOX_UNSELECTED_BORDER_STYLE +// }); + +// let [file_area, keyword_area, count_area] = Layout::horizontal([ +// Constraint::Fill(3), +// Constraint::Fill(4), +// Constraint::Fill(1), +// ]) +// .horizontal_margin(1) +// .areas(area); + +// Line::from(vec![ +// Span::raw("File: ").bold(), +// Span::raw(self.main_bibfile.file_name().unwrap().to_string_lossy()).bold(), +// ]) +// .bg(HEADER_FOOTER_BG) +// .render(file_area, buf); + +// Line::from(if !self.tag_list.selected_keywords.is_empty() { +// vec![ +// Span::raw("Selected keywords: "), +// // Show all keywords in correct order if list is filtered +// // successively by multiple keywords +// Span::raw(self.tag_list.selected_keywords.join(" → ")) +// .bold() +// .green(), +// ] +// } else { +// vec![Span::raw(" ")] +// }) +// .bg(HEADER_FOOTER_BG) +// .render(keyword_area, buf); + +// Line::from(if self.entry_table.entry_table_state.selected().is_some() { +// vec![ +// Span::raw((self.entry_table.entry_table_state.selected().unwrap() + 1).to_string()) +// .bold(), +// Span::raw("/"), +// Span::raw(self.entry_table.entry_table_items.len().to_string()), +// ] +// } else { +// vec![Span::raw("No entries")] +// }) +// .right_aligned() +// .bg(HEADER_FOOTER_BG) +// .render(count_area, buf); + +// // Render that stuff +// Widget::render(block, area, buf) +// } + +// pub fn render_entrytable(&mut self, area: Rect, buf: &mut Buffer) { +// let block = Block::new() // can also be Block::new +// .title( +// Line::styled( +// " Bibliographic Entries ", +// if let CurrentArea::EntryArea = self.current_area { +// BOX_SELECTED_TITLE_STYLE +// } else { +// BOX_UNSELECTED_TITLE_STYLE +// }, +// ) +// .centered(), +// ) +// .borders(Borders::LEFT | Borders::RIGHT | Borders::TOP) +// .border_set(if let CurrentArea::EntryArea = self.current_area { +// symbols::border::THICK +// } else { +// symbols::border::PLAIN +// }) +// .border_style(if let CurrentArea::EntryArea = self.current_area { +// BOX_SELECTED_BOX_STYLE +// } else { +// BOX_UNSELECTED_BORDER_STYLE +// }); + +// let header_style = Style::default() +// .bold() +// .fg(TEXT_FG_COLOR) +// .bg(HEADER_FOOTER_BG); + +// let header_selected_col = Style::default().underlined(); + +// let header = Row::new(vec![ +// Cell::from(Line::from(vec![ +// { +// if let EntryTableColumn::Authors = self.entry_table.entry_table_selected_column +// { +// Span::styled("Author", header_selected_col) +// } else { +// Span::raw("Author") +// } +// }, +// { +// if let EntryTableColumn::Authors = self.entry_table.entry_table_sorted_by_col { +// Span::raw(format!( +// " {}", +// if self.entry_table.entry_table_reversed_sort { +// SORTED_ENTRIES_REVERSED +// } else { +// SORTED_ENTRIES +// } +// )) +// } else { +// Span::raw("") +// } +// }, +// ])), +// Cell::from(Line::from(vec![ +// { +// if let EntryTableColumn::Title = self.entry_table.entry_table_selected_column { +// Span::styled("Title", header_selected_col) +// } else { +// Span::raw("Title") +// } +// }, +// { +// if let EntryTableColumn::Title = self.entry_table.entry_table_sorted_by_col { +// Span::raw(format!( +// " {}", +// if self.entry_table.entry_table_reversed_sort { +// SORTED_ENTRIES_REVERSED +// } else { +// SORTED_ENTRIES +// } +// )) +// } else { +// Span::raw("") +// } +// }, +// ])), +// Cell::from(Line::from(vec![ +// { +// if let EntryTableColumn::Year = self.entry_table.entry_table_selected_column { +// Span::styled("Year", header_selected_col) +// } else { +// Span::raw("Year") +// } +// }, +// { +// if let EntryTableColumn::Year = self.entry_table.entry_table_sorted_by_col { +// Span::raw(format!( +// " {}", +// if self.entry_table.entry_table_reversed_sort { +// SORTED_ENTRIES_REVERSED +// } else { +// SORTED_ENTRIES +// } +// )) +// } else { +// Span::raw("") +// } +// }, +// ])), +// Cell::from(Line::from(vec![ +// { +// if let EntryTableColumn::Pubtype = self.entry_table.entry_table_selected_column +// { +// Span::styled("Pubtype", header_selected_col) +// } else { +// Span::raw("Pubtype") +// } +// }, +// { +// if let EntryTableColumn::Pubtype = self.entry_table.entry_table_sorted_by_col { +// Span::raw(format!( +// " {}", +// if self.entry_table.entry_table_reversed_sort { +// SORTED_ENTRIES_REVERSED +// } else { +// SORTED_ENTRIES +// } +// )) +// } else { +// Span::raw("") +// } +// }, +// ])), +// ]) +// .style(header_style) +// .height(1); + +// // Iterate over vector storing each entries data fields +// let rows = self +// .entry_table +// .entry_table_items +// .iter_mut() +// .enumerate() +// .map(|(_i, data)| { +// let item = data.ref_vec(); +// item.into_iter() +// .map(|content| Cell::from(Text::from(format!("{content}")))) +// .collect::<Row>() +// .style(Style::new().fg(TEXT_FG_COLOR)) //.bg(alternate_colors(i))) +// .height(1) +// }); +// let entry_table = Table::new( +// rows, +// [ +// Constraint::Percentage(20), +// Constraint::Fill(1), +// Constraint::Length( +// if let EntryTableColumn::Year = self.entry_table.entry_table_sorted_by_col { +// 6 +// } else { +// 4 +// }, +// ), +// Constraint::Percentage(10), +// ], +// ) +// .block(block) +// .header(header) +// .column_spacing(2) +// .row_highlight_style(SELECTED_STYLE) +// // .bg(Color::Black) +// .highlight_spacing(HighlightSpacing::Always); +// StatefulWidget::render( +// entry_table, +// area, +// buf, +// &mut self.entry_table.entry_table_state, +// ); + +// // Scrollbar for entry table +// let scrollbar = Scrollbar::default() +// .orientation(ScrollbarOrientation::VerticalRight) +// .track_symbol(None) +// .begin_symbol(SCROLLBAR_UPPER_CORNER) +// .end_symbol(None) +// .thumb_style(Style::new().fg(Color::DarkGray)); + +// if let CurrentArea::EntryArea = self.current_area { +// // render the scrollbar +// StatefulWidget::render( +// scrollbar, +// area, +// buf, +// &mut self.entry_table.entry_scroll_state, +// ); +// } +// } + +// pub fn render_selected_item(&mut self, area: Rect, buf: &mut Buffer) { +// // We get the info depending on the item's state. +// let style_value = Style::new().bold().fg(TEXT_FG_COLOR); +// let style_value_sec = Style::new() +// .add_modifier(Modifier::ITALIC) +// .fg(TEXT_FG_COLOR); +// let lines = { +// // if self.entry_table.entry_table_items.len() > 0 { +// if self.entry_table.entry_table_state.selected().is_some() { +// let idx = self.entry_table.entry_table_state.selected().unwrap(); +// let cur_entry = &self.entry_table.entry_table_items[idx]; +// let mut lines = vec![]; +// lines.push(Line::from(vec![ +// Span::styled("Authors: ", style_value), +// // Span::styled(cur_entry.authors.clone(), Style::new().green()), +// Span::styled(cur_entry.authors(), Style::new().green()), +// ])); +// lines.push(Line::from(vec![ +// Span::styled("Title: ", style_value), +// Span::styled(cur_entry.title(), Style::new().magenta()), +// ])); +// lines.push(Line::from(vec![ +// Span::styled("Year: ", style_value), +// Span::styled(cur_entry.year(), Style::new().light_magenta()), +// ])); +// // Render keywords in info box in Markdown code style +// if !cur_entry.keywords.is_empty() { +// let kw: Vec<&str> = cur_entry +// .keywords +// .split(",") +// .map(|k| k.trim()) +// .filter(|k| !k.is_empty()) +// .collect(); +// let mut content = vec![Span::styled("Keywords: ", style_value)]; +// for k in kw { +// // Add half block highlighted in bg color to enlarge block +// content.push(Span::raw("▐").fg(HEADER_FOOTER_BG)); +// content.push(Span::styled( +// k, +// Style::default().bg(HEADER_FOOTER_BG).fg( +// // Highlight selected keyword green +// if self.tag_list.selected_keywords.iter().any(|e| e == k) { +// Color::Green +// } else { +// TEXT_FG_COLOR +// }, +// ), +// )); +// content.push(Span::raw("▌").fg(HEADER_FOOTER_BG)); +// } +// lines.push(Line::from(content)) +// } +// if !cur_entry.doi_url.is_empty() || !cur_entry.filepath.is_empty() { +// lines.push(Line::raw("")); +// } +// if !cur_entry.doi_url.is_empty() { +// lines.push(Line::from(vec![ +// Span::styled("DOI/URL: ", style_value_sec), +// Span::styled( +// cur_entry.doi_url(), +// Style::default().fg(TEXT_FG_COLOR).underlined(), +// ), +// ])); +// } +// if !cur_entry.filepath.is_empty() { +// lines.push(Line::from(vec![ +// Span::styled("File: ", style_value_sec), +// Span::styled(cur_entry.filepath(), Style::default().fg(TEXT_FG_COLOR)), +// ])); +// } +// lines.push(Line::from("")); +// lines.push(Line::from(vec![Span::styled( +// cur_entry.abstract_text.clone(), +// Style::default().fg(TEXT_FG_COLOR), +// )])); +// lines +// } else { +// let lines = vec![ +// Line::from(" "), +// Line::from("No entry selected".bold().into_centered_line().red()), +// ]; +// lines +// } +// }; +// let info = Text::from(lines); + +// // We show the list item's info under the list in this paragraph +// let block = Block::bordered() +// .title(Line::raw(" Entry Information ").centered().bold()) +// // .borders(Borders::TOP) +// .border_set(symbols::border::PLAIN) +// .border_style(BOX_UNSELECTED_BORDER_STYLE) +// // .bg(Color::Black) +// .padding(Padding::horizontal(1)); + +// // INFO: '.line_count' method only possible with unstable-rendered-line-info feature -> API might change: https://github.com/ratatui/ratatui/issues/293#ref-pullrequest-2027056434 +// let box_height = Paragraph::new(info.clone()) +// .block(block.clone()) +// .wrap(Wrap { trim: false }) +// .line_count(area.width); +// // Make sure to allow scroll only if text is larger than the rendered area and stop scrolling when last line is reached +// let scroll_height = { +// if self.entry_table.entry_info_scroll == 0 { +// self.entry_table.entry_info_scroll +// } else if area.height > box_height as u16 { +// self.entry_table.entry_info_scroll = 0; +// self.entry_table.entry_info_scroll +// } else if self.entry_table.entry_info_scroll > (box_height as u16 + 2 - area.height) { +// self.entry_table.entry_info_scroll = box_height as u16 + 2 - area.height; +// self.entry_table.entry_info_scroll +// } else { +// self.entry_table.entry_info_scroll +// } +// }; + +// // We can now render the item info +// Paragraph::new(info) +// .block( +// block +// // Render arrows to show that info box has content outside the block +// .title_bottom( +// Line::from( +// if box_height > area.height.into() +// && self.entry_table.entry_info_scroll +// < box_height as u16 + 2 - area.height +// { +// " ▼ " +// } else { +// "" +// }, +// ) +// .alignment(Alignment::Right), +// ) +// .title_top( +// Line::from(if scroll_height > 0 { " ▲ " } else { "" }) +// .alignment(Alignment::Right), +// ), +// ) +// // .fg(TEXT_FG_COLOR) +// .wrap(Wrap { trim: false }) +// .scroll((scroll_height, 0)) +// .render(area, buf); +// } + +// pub fn render_taglist(&mut self, area: Rect, buf: &mut Buffer) { +// let block = Block::bordered() +// .title( +// Line::styled( +// " Keywords ", +// if let CurrentArea::TagArea = self.current_area { +// BOX_SELECTED_TITLE_STYLE +// } else { +// BOX_UNSELECTED_TITLE_STYLE +// }, +// ) +// .centered(), +// ) +// .border_set(if let CurrentArea::TagArea = self.current_area { +// symbols::border::THICK +// } else { +// symbols::border::PLAIN +// }) +// .border_style(if let CurrentArea::TagArea = self.current_area { +// BOX_SELECTED_BOX_STYLE +// } else { +// BOX_UNSELECTED_BORDER_STYLE +// }); +// // .bg(Color::Black); + +// // Iterate through all elements in the `items` and stylize them. +// let items: Vec<ListItem> = self +// .tag_list +// .tag_list_items +// .iter() +// .enumerate() +// .map(|(_i, todo_item)| { +// // let color = alternate_colors(i); +// ListItem::from(todo_item.to_owned()) //.bg(color) +// }) +// .collect(); + +// // Create a List from all list items and highlight the currently selected one +// let list = List::new(items) +// .block(block) +// .highlight_style(SELECTED_STYLE) +// // .highlight_symbol("> ") +// .highlight_spacing(HighlightSpacing::Always); + +// // Save list length for calculating scrollbar need +// // Add 2 to compmensate lines of the block border +// let list_length = list.len() + 2; + +// // We need to disambiguate this trait method as both `Widget` and `StatefulWidget` share the +// // same method name `render`. +// StatefulWidget::render(list, area, buf, &mut self.tag_list.tag_list_state); + +// // Scrollbar for keyword list +// let scrollbar = Scrollbar::default() +// .orientation(ScrollbarOrientation::VerticalRight) +// .track_symbol(None) +// .begin_symbol(SCROLLBAR_UPPER_CORNER) +// .end_symbol(SCROLLBAR_LOWER_CORNER) +// .thumb_style(Style::new().fg(Color::DarkGray)); + +// if list_length > area.height.into() { +// if let CurrentArea::TagArea = self.current_area { +// // render the scrollbar +// StatefulWidget::render(scrollbar, area, buf, &mut self.tag_list.tag_scroll_state); +// } +// } +// } +// } |
