First commit in rust
This commit is contained in:
147
backchannel-client/src/tui/views/channel.rs
Normal file
147
backchannel-client/src/tui/views/channel.rs
Normal file
@@ -0,0 +1,147 @@
|
||||
use ratatui::{
|
||||
layout::{Constraint, Direction, Layout},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
|
||||
Frame,
|
||||
};
|
||||
|
||||
use crate::tui::app::App;
|
||||
|
||||
pub fn draw(frame: &mut Frame, app: &App) {
|
||||
let area = frame.size();
|
||||
|
||||
// Split into sidebar | main column
|
||||
let columns = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.constraints([Constraint::Length(22), Constraint::Min(0)])
|
||||
.split(area);
|
||||
|
||||
draw_sidebar(frame, app, columns[0]);
|
||||
draw_main(frame, app, columns[1]);
|
||||
}
|
||||
|
||||
fn draw_sidebar(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) {
|
||||
let rows = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([Constraint::Min(0), Constraint::Length(6)])
|
||||
.split(area);
|
||||
|
||||
// ── Channel list ────────────────────────────────────────────────────
|
||||
let items: Vec<ListItem> = app
|
||||
.channels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (_, name))| {
|
||||
let style = if i == app.selected_channel {
|
||||
Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD)
|
||||
} else {
|
||||
Style::default()
|
||||
};
|
||||
ListItem::new(format!("# {}", name)).style(style)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut state = ListState::default();
|
||||
state.select(Some(app.selected_channel));
|
||||
|
||||
let channels = List::new(items)
|
||||
.block(
|
||||
Block::default()
|
||||
.title(" Channels ")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Color::Cyan)),
|
||||
)
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||
|
||||
frame.render_stateful_widget(channels, rows[0], &mut state);
|
||||
|
||||
// ── Online users ─────────────────────────────────────────────────────
|
||||
let user_items: Vec<ListItem> = app
|
||||
.online_users
|
||||
.iter()
|
||||
.map(|(_, name)| ListItem::new(format!("● {}", name)))
|
||||
.collect();
|
||||
|
||||
let online = List::new(user_items).block(
|
||||
Block::default()
|
||||
.title(" Online ")
|
||||
.borders(Borders::ALL)
|
||||
.style(Style::default().fg(Color::Green)),
|
||||
);
|
||||
frame.render_widget(online, rows[1]);
|
||||
}
|
||||
|
||||
fn draw_main(frame: &mut Frame, app: &App, area: ratatui::layout::Rect) {
|
||||
let rows = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.constraints([
|
||||
Constraint::Length(1), // channel title bar
|
||||
Constraint::Min(0), // messages
|
||||
Constraint::Length(3), // input box
|
||||
Constraint::Length(1), // status bar
|
||||
])
|
||||
.split(area);
|
||||
|
||||
// ── Title bar ────────────────────────────────────────────────────────
|
||||
let title = app
|
||||
.current_channel()
|
||||
.map(|(_, name)| format!(" # {} ", name))
|
||||
.unwrap_or_else(|| " No channel selected ".into());
|
||||
|
||||
let title_bar = Paragraph::new(title)
|
||||
.style(Style::default().fg(Color::White).bg(Color::DarkGray).add_modifier(Modifier::BOLD));
|
||||
frame.render_widget(title_bar, rows[0]);
|
||||
|
||||
// ── Messages ─────────────────────────────────────────────────────────
|
||||
let msgs: Vec<ListItem> = app
|
||||
.current_channel_messages()
|
||||
.map(|deque| {
|
||||
deque
|
||||
.iter()
|
||||
.map(|m| {
|
||||
ListItem::new(Line::from(vec![
|
||||
Span::styled(
|
||||
format!("{} ", m.timestamp),
|
||||
Style::default().fg(Color::DarkGray),
|
||||
),
|
||||
Span::styled(
|
||||
format!("{}: ", m.author),
|
||||
Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
|
||||
),
|
||||
Span::raw(m.content.clone()),
|
||||
]))
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let message_count = msgs.len();
|
||||
let mut list_state = ListState::default();
|
||||
if message_count > 0 {
|
||||
list_state.select(Some(message_count - 1));
|
||||
}
|
||||
|
||||
let messages = List::new(msgs)
|
||||
.block(Block::default().borders(Borders::LEFT))
|
||||
.highlight_style(Style::default());
|
||||
frame.render_stateful_widget(messages, rows[1], &mut list_state);
|
||||
|
||||
// ── Input box ─────────────────────────────────────────────────────────
|
||||
let input = Paragraph::new(app.input.as_str())
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title(" Message (Enter to send) "),
|
||||
);
|
||||
frame.render_widget(input, rows[2]);
|
||||
|
||||
// ── Status bar ────────────────────────────────────────────────────────
|
||||
let status_text = app
|
||||
.status_msg
|
||||
.as_deref()
|
||||
.unwrap_or("Ctrl-Q: quit | ↑↓: channels | Esc: dismiss");
|
||||
let status = Paragraph::new(status_text)
|
||||
.style(Style::default().fg(Color::DarkGray));
|
||||
frame.render_widget(status, rows[3]);
|
||||
}
|
||||
Reference in New Issue
Block a user