use std::sync::atomic::Ordering;
use ratatui::{
Frame,
layout::Rect,
style::{Color, Modifier, Style},
};
use crate::app::App;
use crate::input::VimMode;
use crate::ui::{gradient, theme};
use crate::voip::CallState;
struct Section {
text: String,
fg: Color,
bg: Color,
bold: bool,
}
pub fn render(app: &App, frame: &mut Frame, area: Rect) {
let icons = app.config.icons();
let powerline = icons.powerline_right;
let (mode_fg, mode_bg) = match app.vim.mode {
VimMode::Normal => (theme::BLACK, theme::NORMAL_MODE_BG),
VimMode::Insert => (theme::BLACK, theme::INSERT_MODE_BG),
VimMode::Command => (theme::BLACK, theme::COMMAND_MODE_BG),
};
let mode_bg_dim = gradient::scale_color(mode_bg, 0.4);
let mode_label = format!(" {} ", app.vim.mode);
let room_name = app
.room_list
.selected_room()
.map(|r| format!(" {} ", r.name))
.unwrap_or_default();
let sync_status = format!(" {} ", app.sync_status);
let mut sections: Vec<Section> = Vec::new();
sections.push(Section {
text: mode_label,
fg: mode_fg,
bg: mode_bg,
bold: true,
});
if !room_name.is_empty() {
sections.push(Section {
text: room_name,
fg: theme::TEXT,
bg: theme::STATUS_BAR_BG,
bold: false,
});
}
sections.push(Section {
text: sync_status,
fg: theme::DIM,
bg: theme::STATUS_BAR_BG,
bold: false,
});
let participants_label = |info: &crate::voip::CallInfo| -> String {
if info.participants.is_empty() {
String::new()
} else if info.participants.len() == 1 {
info.participants[0].clone()
} else {
format!("{} participants", info.participants.len())
}
};
if let Some(ref info) = app.call_info {
let label = participants_label(info);
match info.state {
CallState::Connecting(_) => {
sections.push(Section {
text: format!(" CONNECTING: {} ", label),
fg: theme::CYAN,
bg: theme::STATUS_BAR_BG,
bold: true,
});
}
CallState::Active => {
sections.push(Section {
text: format!(" CALL {} {} ", info.elapsed_display(), label),
fg: theme::GREEN,
bg: theme::STATUS_BAR_BG,
bold: true,
});
}
}
} else if let Some(ref caller) = app.incoming_call_user {
sections.push(Section {
text: format!(" INCOMING: {} ", caller),
fg: theme::GREEN,
bg: theme::STATUS_BAR_BG,
bold: true,
});
}
if let Some(ref info) = app.call_info
&& matches!(info.state, CallState::Active)
{
if app.mic_active.load(Ordering::Relaxed) {
sections.push(Section {
text: " \u{25cf} MIC".to_string(),
fg: theme::GREEN,
bg: theme::STATUS_BAR_BG,
bold: true,
});
} else {
sections.push(Section {
text: " \u{25cb} MIC".to_string(),
fg: theme::DIM,
bg: theme::STATUS_BAR_BG,
bold: false,
});
}
}
if let Some(ref modal) = app.verification_modal {
sections.push(Section {
text: format!(" VERIFYING: {} ", modal.sender),
fg: theme::CYAN,
bg: theme::STATUS_BAR_BG,
bold: true,
});
}
if let Some(ref err) = app.last_error {
sections.push(Section {
text: format!(" {} ", err),
fg: theme::RED,
bg: theme::STATUS_BAR_BG,
bold: false,
});
}
let buf = frame.buffer_mut();
let bounds = *buf.area();
let bar_style = Style::default().bg(theme::STATUS_BAR_BG);
for x in area.x..area.x + area.width {
if x < bounds.x + bounds.width && area.y < bounds.y + bounds.height {
let cell = &mut buf[(x, area.y)];
cell.set_char(' ');
cell.set_style(bar_style);
}
}
let mut cursor_x = area.x;
for (i, section) in sections.iter().enumerate() {
if section.text.is_empty() {
continue;
}
let section_bg = if i == 0 {
let char_count = section.text.chars().count();
for (ci, ch) in section.text.chars().enumerate() {
if cursor_x + ci as u16 >= area.x + area.width {
break;
}
let x = cursor_x + ci as u16;
if x < bounds.x + bounds.width && area.y < bounds.y + bounds.height {
let t = ci as f32 / char_count.max(1) as f32;
let bg = gradient::lerp_color(section.bg, mode_bg_dim, t);
let mut style = Style::default().fg(section.fg).bg(bg);
if section.bold {
style = style.add_modifier(Modifier::BOLD);
}
let cell = &mut buf[(x, area.y)];
cell.set_char(ch);
cell.set_style(style);
}
}
cursor_x += char_count as u16;
mode_bg_dim
} else {
let mut style = Style::default().fg(section.fg).bg(section.bg);
if section.bold {
style = style.add_modifier(Modifier::BOLD);
}
for ch in section.text.chars() {
if cursor_x >= area.x + area.width {
break;
}
if cursor_x < bounds.x + bounds.width && area.y < bounds.y + bounds.height {
let cell = &mut buf[(cursor_x, area.y)];
cell.set_char(ch);
cell.set_style(style);
}
cursor_x += 1;
}
section.bg
};
let next_bg = sections
.get(i + 1)
.map(|s| s.bg)
.unwrap_or(theme::STATUS_BAR_BG);
if cursor_x < area.x + area.width
&& cursor_x < bounds.x + bounds.width
&& area.y < bounds.y + bounds.height
{
let sep_style = Style::default().fg(section_bg).bg(next_bg);
let cell = &mut buf[(cursor_x, area.y)];
cell.set_char(powerline.chars().next().unwrap_or('▸'));
cell.set_style(sep_style);
cursor_x += 1;
}
}
}