add sync, ui stuff

This commit is contained in:
cool-mist 2025-09-10 22:22:57 +05:30
parent 23f912d845
commit f30ef8b06c
5 changed files with 65 additions and 52 deletions

View File

@ -25,7 +25,7 @@ use debug::DebugWidget;
use futures::{FutureExt, StreamExt}; use futures::{FutureExt, StreamExt};
use ratatui::{ use ratatui::{
prelude::*, prelude::*,
widgets::{Block, Borders}, widgets::{Block, Borders, Padding},
}; };
use title::TitleWidget; use title::TitleWidget;
use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedReceiver;
@ -86,7 +86,6 @@ pub enum UiCommandDispatchActions {
ListChannels(args::ListChannelArgs), ListChannels(args::ListChannelArgs),
} }
/// APP /// APP
/// - Listen Event /// - Listen Event
/// - Publish UiCommandDispatchActions /// - Publish UiCommandDispatchActions
@ -142,7 +141,11 @@ pub async fn ui(args: &UiArgs, db_name: &str) -> Result<()> {
Ok(()) Ok(())
} }
fn start_backend(db_name: &str, app_recv: std::sync::mpsc::Receiver<UiCommandDispatchActions>, executor_dispatch: tokio::sync::mpsc::UnboundedSender<BackendEvent>) { fn start_backend(
db_name: &str,
app_recv: std::sync::mpsc::Receiver<UiCommandDispatchActions>,
executor_dispatch: tokio::sync::mpsc::UnboundedSender<BackendEvent>,
) {
let db_name = db_name.to_string(); let db_name = db_name.to_string();
std::thread::spawn(move || { std::thread::spawn(move || {
backend::start(db_name, app_recv, executor_dispatch); backend::start(db_name, app_recv, executor_dispatch);
@ -227,6 +230,9 @@ async fn handle_events(state: &mut AppState) -> Result<()> {
Event::BackendEvent(backend_event) => match backend_event { Event::BackendEvent(backend_event) => match backend_event {
BackendEvent::ReloadState(channels) => { BackendEvent::ReloadState(channels) => {
state.channels = channels; state.channels = channels;
if state.highlighted_channel.is_none() && !state.channels.is_empty() {
state.highlighted_channel = Some(0);
}
} }
}, },
Event::Tick => {} Event::Tick => {}
@ -260,9 +266,9 @@ impl<'a> AppStateWidget<'a> {
impl<'a> Widget for AppStateWidget<'a> { impl<'a> Widget for AppStateWidget<'a> {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let mut horizontal_constraints = vec![Constraint::Percentage(100)]; let mut horizontal_constraints = vec![Constraint::Fill(5)];
if is_debug_mode(self.app_state) { if is_debug_mode(self.app_state) {
horizontal_constraints.push(Constraint::Percentage(20)); horizontal_constraints.push(Constraint::Fill(1));
} }
// Split the area into 2 horizontal sections, one for the main app and // Split the area into 2 horizontal sections, one for the main app and
@ -282,24 +288,20 @@ impl<'a> Widget for AppStateWidget<'a> {
let main_area_splits = Layout::default() let main_area_splits = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.constraints(vec![ .constraints(vec![
Constraint::Percentage(10), // Title Constraint::Percentage(100), // Channels + Articles
Constraint::Percentage(80), // Other app widgets Constraint::Min(4), // Controls + Title
Constraint::Percentage(10), // Controls
]) ])
.split(main_area) .split(main_area)
.to_vec(); .to_vec();
// TITLE
let title_area = main_area_splits[0];
draw_app_widget_styled(Block::default(), &title_area, buf, TitleWidget);
// OTHER APP WIDGETS // OTHER APP WIDGETS
let child_widgets_areas = Layout::default() let child_widgets_areas = Layout::default()
.direction(Direction::Horizontal) .direction(Direction::Horizontal)
.constraints(Constraint::from_percentages(vec![30, 70])) .constraints(Constraint::from_fills([4, 6]))
.split(main_area_splits[1]) .split(main_area_splits[0])
.to_vec(); .to_vec();
// CHANNELS
let channels_area = child_widgets_areas[0]; let channels_area = child_widgets_areas[0];
draw_app_widget_styled( draw_app_widget_styled(
get_child_widget_style( get_child_widget_style(
@ -311,6 +313,7 @@ impl<'a> Widget for AppStateWidget<'a> {
ChannelsWidget::new(self.app_state), ChannelsWidget::new(self.app_state),
); );
// ARTICLES
let articles_area = child_widgets_areas[1]; let articles_area = child_widgets_areas[1];
draw_app_widget_styled( draw_app_widget_styled(
get_child_widget_style( get_child_widget_style(
@ -322,8 +325,17 @@ impl<'a> Widget for AppStateWidget<'a> {
ArticlesWidget::new(self.app_state), ArticlesWidget::new(self.app_state),
); );
let controls_title = Layout::default()
.direction(Direction::Horizontal)
.constraints(Constraint::from_fills([1, 7]))
.split(main_area_splits[1]);
// TITLE
let title_area = controls_title[0];
draw_app_widget_styled(Block::default(), &title_area, buf, TitleWidget);
// CONTROLS // CONTROLS
let controls_area = main_area_splits[2]; let controls_area = controls_title[1];
draw_app_widget_styled(Block::default(), &controls_area, buf, ControlsWidget); draw_app_widget_styled(Block::default(), &controls_area, buf, ControlsWidget);
} }
} }
@ -361,18 +373,20 @@ where
fn get_child_widget_style<'a>(arg: &'a str, focussed: bool) -> Block<'a> { fn get_child_widget_style<'a>(arg: &'a str, focussed: bool) -> Block<'a> {
let title = Line::from(arg) let title = Line::from(arg)
.centered() .style(
.style(Style::default().fg(Color::Red).add_modifier(Modifier::BOLD)); Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
)
.centered();
if focussed { if focussed {
return Block::default() return Block::default().title_top(title).bg(Color::DarkGray);
.title_top(title)
.border_style(Style::default().fg(Color::Blue))
.borders(Borders::ALL);
} }
Block::default() Block::default()
.title_top(title) .title_top(title)
.fg(Color::DarkGray)
.padding(Padding::uniform(10))
.border_style(Style::default().fg(Color::DarkGray)) .border_style(Style::default().fg(Color::DarkGray))
} }

View File

@ -3,7 +3,7 @@ use ratatui::{
layout::{Alignment, Constraint, Direction, Layout, Rect}, layout::{Alignment, Constraint, Direction, Layout, Rect},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
text::{Line, Span}, text::{Line, Span},
widgets::{Block, Borders, Paragraph, Widget}, widgets::{Block, Paragraph, Widget},
}; };
use super::AppState; use super::AppState;
@ -26,10 +26,6 @@ impl<'a> Widget for ArticlesWidget<'a> {
}; };
let Some(channel) = selected_channel else { let Some(channel) = selected_channel else {
let para = Paragraph::new("j/k to navigate channels, q to exit")
.block(Block::default().borders(Borders::NONE))
.alignment(Alignment::Center);
para.render(area, buf);
return; return;
}; };
@ -42,7 +38,7 @@ impl<'a> Widget for ArticlesWidget<'a> {
let total_articles = channel.articles.len().min(total_articles as usize); let total_articles = channel.articles.len().min(total_articles as usize);
let article_rows = Layout::default() let article_rows = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(1) .margin(2)
.constraints( .constraints(
(0..total_articles) (0..total_articles)
.map(|_| Constraint::Length(height_per_entry)) .map(|_| Constraint::Length(height_per_entry))
@ -94,7 +90,7 @@ fn get_article_id_style(highlighted: bool) -> Style {
fn get_channel_list_item_block_style(highlighted: bool) -> Style { fn get_channel_list_item_block_style(highlighted: bool) -> Style {
if highlighted { if highlighted {
Style::default() Style::default()
.bg(Color::LightYellow) .bg(Color::White)
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
} else { } else {
Style::default() Style::default()

View File

@ -30,7 +30,7 @@ impl<'a> Widget for ChannelsWidget<'a> {
let total_channels = self.state.channels.len().min(total_channels as usize); let total_channels = self.state.channels.len().min(total_channels as usize);
let channel_rows = Layout::default() let channel_rows = Layout::default()
.direction(Direction::Vertical) .direction(Direction::Vertical)
.margin(1) .margin(2)
.constraints( .constraints(
(0..total_channels) (0..total_channels)
.map(|_| Constraint::Length(height_per_entry)) .map(|_| Constraint::Length(height_per_entry))
@ -109,7 +109,7 @@ fn get_channel_id_style(highlighted: bool) -> Style {
fn get_channel_list_item_block_style(highlighted: bool) -> Style { fn get_channel_list_item_block_style(highlighted: bool) -> Style {
if highlighted { if highlighted {
Style::default() Style::default()
.bg(Color::LightYellow) .bg(Color::White)
.add_modifier(Modifier::BOLD) .add_modifier(Modifier::BOLD)
} else { } else {
Style::default() Style::default()
@ -142,11 +142,11 @@ impl<'a> Widget for AddChannelWidget<'a> {
.title_top(Line::from("Add Channel").centered()) .title_top(Line::from("Add Channel").centered())
.title_style( .title_style(
Style::default() Style::default()
.fg(Color::Blue) .fg(Color::White)
.add_modifier(Modifier::BOLD | Modifier::ITALIC | Modifier::UNDERLINED), .add_modifier(Modifier::BOLD | Modifier::UNDERLINED),
) )
.borders(ratatui::widgets::Borders::ALL) .borders(ratatui::widgets::Borders::ALL)
.border_style(Style::default().fg(Color::DarkGray)); .border_style(Style::default().fg(Color::White));
let para = Paragraph::new(Line::from(self.state)).block(block); let para = Paragraph::new(Line::from(self.state)).block(block);

View File

@ -78,7 +78,9 @@ macro_rules! control {
($key:literal) => { ($key:literal) => {
Span::styled( Span::styled(
$key, $key,
Style::default().fg(Color::Red).add_modifier(Modifier::BOLD), Style::default()
.fg(Color::White)
.add_modifier(Modifier::BOLD),
) )
}; };
} }
@ -91,7 +93,7 @@ macro_rules! description {
impl Widget for ControlsWidget { impl Widget for ControlsWidget {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let controls_text = Line::from(vec![ let controls_text_line_1 = Line::from(vec![
control!("j/k"), control!("j/k"),
description!(" to navigate up/down, "), description!(" to navigate up/down, "),
control!("h/l"), control!("h/l"),
@ -104,6 +106,14 @@ impl Widget for ControlsWidget {
description!(" delete an RSS channel, "), description!(" delete an RSS channel, "),
control!("r"), control!("r"),
description!(" toggle read state of article, "), description!(" toggle read state of article, "),
])
.style(
Style::default()
.fg(Color::DarkGray)
.add_modifier(Modifier::BOLD),
);
let controls_text_line_2 = Line::from(vec![
control!("q"), control!("q"),
description!(" to exit"), description!(" to exit"),
]) ])
@ -111,10 +121,11 @@ impl Widget for ControlsWidget {
Style::default() Style::default()
.fg(Color::DarkGray) .fg(Color::DarkGray)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
); )
let para = Paragraph::new(controls_text) .centered();
let para = Paragraph::new(vec![controls_text_line_1, controls_text_line_2])
.block(Block::default().borders(Borders::NONE)) .block(Block::default().borders(Borders::NONE))
.alignment(Alignment::Center); .alignment(Alignment::Left);
para.render(area, buf); para.render(area, buf);
} }
} }

View File

@ -1,8 +1,8 @@
use ratatui::{ use ratatui::{
buffer::Buffer, buffer::Buffer,
layout::{Alignment, Constraint, Layout, Rect}, layout::{Alignment, Rect},
style::{Color, Modifier, Style}, style::{Color, Modifier, Style},
widgets::{Block, Paragraph, Widget}, widgets::{Block, Borders, Paragraph, Widget},
}; };
pub struct TitleWidget; pub struct TitleWidget;
@ -10,21 +10,13 @@ pub struct TitleWidget;
impl Widget for TitleWidget { impl Widget for TitleWidget {
fn render(self, area: Rect, buf: &mut Buffer) { fn render(self, area: Rect, buf: &mut Buffer) {
let title = "Terminal RSS Manager"; let title = "Terminal RSS Manager";
let areas = Layout::default()
.constraints(Constraint::from_ratios([(1, 3), (1, 3), (1, 3)]))
.split(area)
.to_vec();
let para = Paragraph::new(title).alignment(Alignment::Center).style( let para = Paragraph::new(title).alignment(Alignment::Center).style(
Style::default() Style::default()
.fg(Color::Black) .fg(Color::White)
.add_modifier(Modifier::BOLD), .add_modifier(Modifier::BOLD),
); );
para.render(areas[1], buf); para.render(area, buf);
Block::default().borders(Borders::RIGHT).render(area, buf);
Block::default()
.style(Style::default().bg(Color::LightCyan))
.render(area, buf);
} }
} }