#[allow(clippy::wildcard_imports)]
use super::*;
pub(in crate::app) fn render_board(app: &App, f: &mut ratatui::Frame, area: Rect, tag: &str) {
let tab_h: u16 = if app.tabs.needs_bar() { TAB_HEIGHT } else { 0 };
let view_h: u16 = u16::from(app.view_indicator().is_some());
let band_h = filter_band_height(app);
let filter_h = band_h - view_h;
let hint_h: u16 = if app.one_shot { 0 } else { HINT_HEIGHT };
let mention_h = mention_row_height(app);
if area.height <= hint_h + mention_h + tab_h + HOVER_HEIGHT + band_h {
return;
}
let hint_area = Rect {
y: area.y + area.height - hint_h,
height: hint_h,
..area
};
let mention_area = Rect {
y: area.y + area.height - hint_h - mention_h,
height: mention_h,
..area
};
let tabs_area = Rect {
y: area.y,
height: tab_h,
..area
};
let view_area = Rect {
y: area.y + tab_h + HOVER_HEIGHT,
height: view_h,
..area
};
let filter_area = Rect {
y: area.y + tab_h + HOVER_HEIGHT + view_h,
height: filter_h,
..area
};
let content = Rect {
y: area.y + tab_h + HOVER_HEIGHT + band_h,
height: area.height - tab_h - HOVER_HEIGHT - band_h - hint_h - mention_h,
..area
};
if tab_h > 0 {
app.tabs.render(f, tabs_area);
for index in 0..app.tabs.len() {
if let Some(rect) = app.tabs.tab_rect(index, tabs_area) {
app.record_region(rect, Action::SwitchBoard(index));
}
}
}
render_hover_line(app, f, area);
if view_h > 0 {
render_view_row(app, f, view_area);
}
if filter_h > 0 {
filter_row(app).render(f, filter_area);
}
let page_cards = board::render(
&app.board,
f,
content,
&app.status_msg,
app.drag_preview_col,
board::BoardDisplay::from(app.active_board_config()),
&app.theme,
Some(&app.wip),
app.capacity_summaries(),
);
app.board_page.set(page_cards);
if mention_h > 0 {
crate::ui::mentions::render_indicator(app, f, mention_area);
}
if hint_h > 0 {
render_hint(f, hint_area, app, tag);
}
}
pub(in crate::app) fn render_detail(app: &App, f: &mut ratatui::Frame, area: Rect, tag: &str) {
let status_h: u16 = if app.status_msg.is_some() { 1 } else { 0 };
if area.height <= HINT_HEIGHT + status_h {
return;
}
let hint_area = Rect {
y: area.y + area.height - HINT_HEIGHT,
height: HINT_HEIGHT,
..area
};
let content = Rect {
height: area.height - status_h - HINT_HEIGHT,
..area
};
if let Some(ref d) = app.detail {
detail::render(d, f, content, &app.theme);
}
if let Some(msg) = &app.status_msg {
let status_area = Rect {
y: area.y + area.height - status_h - HINT_HEIGHT,
height: status_h,
..area
};
let para = Paragraph::new(Line::from(Span::raw(msg.clone())))
.style(Style::default().fg(Color::Yellow));
f.render_widget(para, status_area);
}
if let Some(picker) = &app.related_picker {
render_related_picker(app, picker, f, area);
}
render_hint(f, hint_area, app, tag);
}
pub(in crate::app) fn render_related_picker(
app: &App,
picker: &DropdownState,
f: &mut ratatui::Frame,
area: Rect,
) {
let rect = crate::ui::modal::modal_rect(area);
let picker_rect = Rect::new(
rect.x.saturating_add(2),
rect.y.saturating_add(2),
rect.width.saturating_sub(4).max(10),
rect.height.saturating_sub(4).max(5),
);
f.render_widget(Clear, picker_rect);
const WINDOW: usize = 10;
let visible = picker.visible_indices();
let start = picker.cursor.saturating_sub(WINDOW.saturating_sub(1));
let mut lines: Vec<Line<'static>> = Vec::new();
for (pos, idx) in visible.iter().enumerate().skip(start).take(WINDOW) {
let Some(item) = picker.options.get(*idx) else {
continue;
};
let marker = if pos == picker.cursor { "> " } else { " " };
lines.push(Line::from(format!("{marker}{}", item.label)));
}
if lines.is_empty() {
lines.push(Line::from("(no related issues)"));
}
lines.push(Line::from(""));
lines.push(Line::from(Span::styled(
format!(
"{} select | {} close",
app.bindings
.label(InputContext::RelatedPicker, TuiAction::Submit),
app.bindings
.label(InputContext::RelatedPicker, TuiAction::Cancel),
),
Style::default().fg(Color::DarkGray),
)));
let para = Paragraph::new(lines).block(
Block::default()
.borders(Borders::ALL)
.title("Related issues"),
);
f.render_widget(para, picker_rect);
}
pub(in crate::app) fn render_filter_picker(
app: &App,
f: &mut ratatui::Frame,
area: Rect,
picker: &FilterPicker,
) {
let modal = crate::ui::modal::modal_rect(area);
let Some((rect, rows)) = crate::ui::filter_modal::picker_layout(modal, &picker.state) else {
return;
};
app.record_region(area, Action::FilterPickerClose);
f.render_widget(Clear, rect);
let block = Block::default()
.borders(Borders::ALL)
.title(format!(" {} ", picker.field.label()));
let inner = block.inner(rect);
f.render_widget(block, rect);
let lines: Vec<Line<'static>> = picker
.state
.visible_indices()
.iter()
.enumerate()
.map(|(row, index)| {
let label = &picker.state.options[*index].label;
let marker = if row == picker.state.cursor {
"> "
} else {
" "
};
Line::from(format!("{marker}{label}"))
})
.collect();
f.render_widget(Paragraph::new(lines), inner);
for (row, row_rect) in rows.iter().enumerate() {
app.record_region(*row_rect, Action::FilterPickerOption(row));
}
}