use ratatui::Frame;
use ratatui::layout::Rect;
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use crate::app::{App, QUICK_EMOJIS};
use crate::ui::emoji_data::{EmojiCategory, filtered_emojis};
use crate::ui::{popup, theme};
const SLOT_W: u16 = 5;
const MARGIN: u16 = 3;
const GRID_COLS: usize = 8;
const GRID_VISIBLE_ROWS: usize = 6;
pub fn render(app: &App, frame: &mut Frame) {
let Some(ref picker) = app.reaction_picker else {
return;
};
let area = frame.area();
let content_w = SLOT_W * QUICK_EMOJIS.len() as u16;
let popup_h = (11 + GRID_VISIBLE_ROWS as u16).min(area.height);
let popup_w = (content_w + MARGIN * 2).min(area.width);
let popup_rect = popup::centered_rect(popup_w, popup_h, area);
let inner_w = popup_rect.width.saturating_sub(MARGIN * 2);
let filtered = filtered_emojis(&picker.filter);
{
let buf = frame.buffer_mut();
let bounds = area;
popup::render_popup_chrome(buf, &bounds, popup_rect, "React", app.anim_clock.phase);
let hint = if picker.filter_active {
"Enter: apply Esc: clear"
} else if picker.in_grid {
"hjkl: move /: filter Enter: confirm"
} else {
"h/l: move j: grid /: filter Enter: confirm"
};
popup::render_hint(buf, &bounds, popup_rect, hint);
}
let quick_y = popup_rect.y + 2;
{
let mut emoji_spans: Vec<Span> = Vec::new();
for (i, emoji) in QUICK_EMOJIS.iter().enumerate() {
let is_selected =
!picker.in_grid && !picker.filter_active && i == picker.quick_pick_index;
let is_own = picker.existing_own_reactions.contains(&emoji.to_string());
let style = if is_selected {
theme::highlight_focused_style()
} else if is_own {
theme::reaction_own_badge_style()
} else {
theme::text_style()
};
emoji_spans.push(Span::styled(format!(" {} ", emoji), style));
emoji_spans.push(Span::raw(" "));
}
let emoji_rect = Rect {
x: popup_rect.x + MARGIN,
y: quick_y,
width: inner_w,
height: 1,
};
frame.render_widget(Paragraph::new(Line::from(emoji_spans)), emoji_rect);
let mut hint_spans: Vec<Span> = Vec::new();
for i in 1..=QUICK_EMOJIS.len() {
hint_spans.push(Span::styled(
format!(" {:<width$}", i, width = SLOT_W as usize - 1),
theme::dim_style(),
));
}
let hint_rect = Rect {
x: popup_rect.x + MARGIN,
y: quick_y + 1,
width: inner_w,
height: 1,
};
frame.render_widget(Paragraph::new(Line::from(hint_spans)), hint_rect);
}
let filter_y = quick_y + 3;
if picker.filter_active || !picker.filter.is_empty() {
let buf = frame.buffer_mut();
let bounds = area;
let label = "Filter: ";
popup::write_str(
buf,
&bounds,
popup_rect.x + MARGIN,
filter_y,
label,
theme::dim_style(),
);
popup::write_str(
buf,
&bounds,
popup_rect.x + MARGIN + label.len() as u16,
filter_y,
&picker.filter,
if picker.filter_active {
theme::text_style()
} else {
theme::dim_style()
},
);
if picker.filter_active {
let cursor_x = popup_rect.x + MARGIN + label.len() as u16 + picker.filter.len() as u16;
popup::write_str(buf, &bounds, cursor_x, filter_y, "_", theme::text_style());
}
}
let grid_y = filter_y + 1;
let grid_rows_available = popup_rect
.height
.saturating_sub(grid_y - popup_rect.y)
.saturating_sub(3) as usize;
if !filtered.is_empty() && grid_rows_available > 0 {
let total_emoji_rows = filtered.len().div_ceil(GRID_COLS);
let selected_emoji_row = if picker.in_grid {
picker.grid_index / GRID_COLS
} else {
0
};
struct VisualRow {
category: Option<&'static str>,
emoji_start: usize, emoji_count: usize, }
let mut visual_rows: Vec<VisualRow> = Vec::new();
let mut i = 0;
let mut last_cat: Option<EmojiCategory> = None;
while i < filtered.len() {
let cat = filtered[i].category;
if last_cat != Some(cat) {
visual_rows.push(VisualRow {
category: Some(cat.label()),
emoji_start: i,
emoji_count: 0,
});
last_cat = Some(cat);
}
let row_end = (i + GRID_COLS).min(filtered.len());
visual_rows.push(VisualRow {
category: None,
emoji_start: i,
emoji_count: row_end - i,
});
i = row_end;
}
let selected_visual_row = if picker.in_grid {
visual_rows
.iter()
.position(|vr| {
vr.emoji_count > 0
&& picker.grid_index >= vr.emoji_start
&& picker.grid_index < vr.emoji_start + vr.emoji_count
})
.unwrap_or(0)
} else {
0
};
let scroll = {
let max_scroll = visual_rows.len().saturating_sub(grid_rows_available);
let mut s = picker.scroll_offset.min(max_scroll);
if picker.in_grid {
if selected_visual_row < s {
s = selected_visual_row;
} else if selected_visual_row >= s + grid_rows_available {
s = selected_visual_row + 1 - grid_rows_available;
}
}
s.min(max_scroll)
};
let visible_end = (scroll + grid_rows_available).min(visual_rows.len());
for (vi, vr) in visual_rows[scroll..visible_end].iter().enumerate() {
let row_y = grid_y + vi as u16;
if row_y >= popup_rect.y + popup_rect.height - 2 {
break;
}
if let Some(cat_label) = vr.category {
let buf = frame.buffer_mut();
let bounds = area;
let header = format!("── {} ", cat_label);
let padded: String = if header.len() < inner_w as usize {
let remaining = inner_w as usize - header.len();
format!("{}{}", header, "─".repeat(remaining))
} else {
header
};
popup::write_str(
buf,
&bounds,
popup_rect.x + MARGIN,
row_y,
&padded,
theme::dim_style(),
);
} else {
let mut spans: Vec<Span> = Vec::new();
for col in 0..GRID_COLS {
let emoji_idx = vr.emoji_start + col;
if col >= vr.emoji_count {
break;
}
let entry = &filtered[emoji_idx];
let is_selected = picker.in_grid && emoji_idx == picker.grid_index;
let is_own = picker
.existing_own_reactions
.contains(&entry.emoji.to_string());
let style = if is_selected {
theme::highlight_focused_style()
} else if is_own {
theme::reaction_own_badge_style()
} else {
theme::text_style()
};
spans.push(Span::styled(format!(" {} ", entry.emoji), style));
spans.push(Span::raw(" "));
}
let row_rect = Rect {
x: popup_rect.x + MARGIN,
y: row_y,
width: inner_w,
height: 1,
};
frame.render_widget(Paragraph::new(Line::from(spans)), row_rect);
}
}
let name_y = popup_rect.y + popup_rect.height - 3;
if picker.in_grid && picker.grid_index < filtered.len() {
let name = filtered[picker.grid_index].name;
let buf = frame.buffer_mut();
let bounds = area;
let truncated = popup::truncate_str(name, inner_w as usize);
popup::write_str(
buf,
&bounds,
popup_rect.x + MARGIN,
name_y,
&truncated,
theme::dim_style(),
);
}
let _ = (scroll, total_emoji_rows, selected_emoji_row);
}
}