use crate::prelude::*;
use ratatui::{layout::Flex, prelude::*};
use throbber_widgets_tui::Throbber;
impl Widget for &mut App {
fn render(self, area: Rect, buf: &mut Buffer) {
let mut area = self.render_status_bar(area, buf);
area = self.render_command_line(area.to_owned(), buf);
self.render_panels(area, buf);
if self.help_popup.is_visible() {
self.help_popup.render(area, buf);
}
}
}
impl App {
fn render_status_bar(&self, area: Rect, buf: &mut Buffer) -> Rect {
let render_bottom_bar = !matches!(self.state, AppState::ArticleContentDistractionFree)
|| matches!(
self.tooltip.flavor,
TooltipFlavor::Warning | TooltipFlavor::Error
);
let render_top_bar = self.config.show_top_bar
&& !matches!(self.state, AppState::ArticleContentDistractionFree);
let [top, middle, bottom] = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(if render_top_bar { 1 } else { 0 }), Constraint::Min(0), Constraint::Length(if render_bottom_bar { 1 } else { 0 }), ])
.areas(area);
let status_span = if self.is_offline {
Span::styled(
format!("{} ", self.config.offline_icon),
self.config.theme.statusbar(),
)
} else {
let use_type = if self.news_flash_utils.is_async_operation_running() {
throbber_widgets_tui::WhichUse::Spin
} else {
throbber_widgets_tui::WhichUse::Empty
};
Throbber::default()
.throbber_style(self.config.theme.statusbar())
.style(self.config.theme.statusbar())
.throbber_set(throbber_widgets_tui::BRAILLE_EIGHT_DOUBLE)
.use_type(use_type)
.to_symbol_span(&self.async_operation_throbber)
};
if render_top_bar {
let eilmeldung_span =
Span::styled(" eilmeldung ", self.config.theme.statusbar());
Block::default()
.style(self.config.theme.statusbar())
.render(top, buf);
let [top_left, top_main, top_right] = Layout::default()
.direction(Direction::Horizontal)
.flex(Flex::Center)
.constraints([
Constraint::Length(1),
Constraint::Min(top.width.saturating_sub(2)),
Constraint::Length(1),
])
.areas::<3>(top);
Span::styled("", self.config.theme.statusbar().not_reversed()).render(top_left, buf);
Span::styled("", self.config.theme.statusbar().not_reversed()).render(top_right, buf);
let title = Line::from(vec![eilmeldung_span, status_span.clone()]);
title.render(top_main, buf);
}
if render_bottom_bar {
Block::default()
.style(self.config.theme.statusbar())
.render(bottom, buf);
let status_icon_length = if !render_top_bar { 1 } else { 0 };
let [bottom_left, bottom_main, status, bottom_right] = Layout::default()
.direction(Direction::Horizontal)
.flex(Flex::Center)
.constraints([
Constraint::Length(1),
Constraint::Min(top.width.saturating_sub(2 + status_icon_length)),
Constraint::Length(status_icon_length),
Constraint::Length(1),
])
.areas::<4>(bottom);
let tooltip_line = self.tooltip.to_line(&self.config);
Span::styled(status_span.content, tooltip_line.style).render(status, buf);
Span::styled("", tooltip_line.style.not_reversed()).render(bottom_left, buf);
Span::styled("", tooltip_line.style.not_reversed()).render(bottom_right, buf);
tooltip_line.render(bottom_main, buf);
}
middle
}
fn render_command_line(&mut self, area: Rect, buf: &mut Buffer) -> Rect {
if self.command_input.is_active() || self.command_confirm.is_active() {
let [panels_chunk, command_line_chunk] =
Layout::vertical(vec![Constraint::Min(0), Constraint::Length(3)]).areas::<2>(area);
if self.command_input.is_active() {
self.command_input.render(command_line_chunk, buf);
} else if self.command_confirm.is_active() {
self.command_confirm.render(command_line_chunk, buf);
}
panels_chunk
} else {
area
}
}
fn render_panels(&mut self, area: Rect, buf: &mut Buffer) {
if self.state == AppState::ArticleContentDistractionFree {
self.article_content.render(area, buf);
return;
}
let (feeds_constraint_width, articles_constraint_width) = match self.state {
AppState::FeedSelection => (
self.config.feed_list_focused_width.as_constraint(),
self.config
.feed_list_focused_width
.as_complementary_constraint(area.width),
),
_ => (
self.config
.article_list_focused_width
.as_complementary_constraint(area.width),
self.config.article_list_focused_width.as_constraint(),
),
};
let (articles_constraint_height, article_content_constraint_height) =
if let Some(override_height) = self.articles_height_override {
(Constraint::Length(override_height), Constraint::Min(0))
} else {
match self.state {
AppState::FeedSelection | AppState::ArticleSelection => (
self.config.article_list_focused_height.as_constraint(),
self.config
.article_list_focused_height
.as_complementary_constraint(area.height),
),
_ => (
self.config
.article_content_focused_height
.as_complementary_constraint(area.height),
self.config.article_content_focused_height.as_constraint(),
),
}
};
let [feeds_list_chunk, articles_chunk] = Layout::default()
.direction(Direction::Horizontal)
.constraints(vec![feeds_constraint_width, articles_constraint_width])
.areas::<2>(area);
let [articles_list_chunk, article_content_chunk] = Layout::default()
.direction(Direction::Vertical)
.constraints([
articles_constraint_height,
article_content_constraint_height,
])
.areas(articles_chunk);
*self.panel_areas.feed_list_mut() = feeds_list_chunk;
*self.panel_areas.articles_list_mut() = articles_list_chunk;
*self.panel_areas.article_content_mut() = article_content_chunk;
self.feed_list.render(feeds_list_chunk, buf);
self.articles_list.render(articles_list_chunk, buf);
self.article_content.render(article_content_chunk, buf);
}
}