mod overlays;
mod status_bar;
use overlays::{render_confirm, render_create_form, render_info_popup, render_settings_picker};
use ratatui::{
Frame,
layout::{Constraint, Direction, Layout, Rect}
};
use rust_i18n::t;
use status_bar::render_status_bar;
use crate::tui::{
app::{App, DrillView, NavKind, Pane},
themes::Palette,
widgets::{Widget, account::AccountWidget, help::HelpWidget}
};
pub fn draw(frame: &mut Frame, app: &mut App) {
let size = frame.area();
let palette = app.theme.palette();
let show_account = app.is_widget_enabled("account") && size.height >= 16;
let show_events = app.is_widget_enabled("events") && size.height >= 24;
let mut constraints = Vec::with_capacity(3);
if show_account {
constraints.push(Constraint::Length(3));
}
constraints.push(Constraint::Min(8));
constraints.push(Constraint::Length(3));
let rows = Layout::default()
.direction(Direction::Vertical)
.constraints(constraints)
.split(size);
let mut idx = 0;
if show_account {
AccountWidget::new(true).render(frame, rows[idx], app);
idx += 1;
}
let sidebar_w = crate::tui::widgets::sidebar::width_for(size.width, app.nav_longest_label());
let body = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Length(sidebar_w), Constraint::Min(10)])
.split(rows[idx]);
idx += 1;
crate::tui::widgets::sidebar::render(frame, body[0], app, &palette);
if show_events {
let right = Layout::default()
.direction(Direction::Vertical)
.constraints([Constraint::Min(8), Constraint::Length(7)])
.split(body[1]);
render_content(frame, right[0], app, &palette);
crate::tui::widgets::events::render(frame, right[1], app);
} else {
render_content(frame, body[1], app, &palette);
}
render_status_bar(frame, rows[idx], app, &palette);
draw_modals(frame, size, app, &palette);
}
fn draw_modals(frame: &mut Frame, size: Rect, app: &App, palette: &Palette) {
if app.show_help {
HelpWidget::new().render(frame, size, app);
}
if app.awaiting_confirm() {
render_confirm(frame, size, app, palette);
}
if app.picker_open() {
render_settings_picker(frame, size, app, palette);
}
if app.info_popup_open() {
render_info_popup(frame, size, app, palette);
}
if let Some(form) = app.create_form.as_ref() {
render_create_form(frame, size, form, palette);
}
if let Some(cp) = app.palette.as_ref() {
crate::tui::command_palette::render(frame, size, palette, cp);
}
}
fn render_content(frame: &mut Frame, area: Rect, app: &mut App, palette: &Palette) {
let border = if app.pane == Pane::Content {
palette.accent
} else {
palette.border
};
if app.detail_open {
app.detail_scroll = crate::tui::widgets::details::render(frame, area, app, border);
return;
}
if let Some(view) = app.drill_view() {
let selected = if app.pane == Pane::Content {
view.selected
} else {
usize::MAX
};
render_drill(frame, area, app, view, selected, border, palette);
return;
}
if app.is_loading {
crate::tui::widgets::skeleton::render(
frame,
area,
palette,
&t!("ui.skeleton_resources"),
8,
app.anim_tick
);
return;
}
match app.nav_current() {
Some(NavKind::Create) => {
crate::tui::widgets::create_panel::render(frame, area, app, border);
return;
}
Some(NavKind::Settings) => {
crate::tui::widgets::settings_panel::render(frame, area, app, border);
return;
}
Some(NavKind::Project(index)) => {
if let Some(project) = app.projects.get(index) {
render_project_preview(frame, area, app, project, border, palette);
return;
}
}
_ => {}
}
crate::tui::widgets::resource_list::render(frame, area, app, border);
}
fn render_project_preview(
frame: &mut Frame,
area: Rect,
app: &App,
project: &crate::tui::app::ProjectSummary,
border: ratatui::style::Color,
palette: &Palette
) {
let cards = crate::tui::widgets::resource_cards::project_preview(project);
let title = format!(" {} ({}) ", project.name, project.resource_count());
let selected = if app.pane == Pane::Content {
app.content_selected()
} else {
usize::MAX
};
render_project_panel(frame, area, app, &title, &cards, selected, border, palette);
}
#[allow(clippy::too_many_arguments)]
fn render_project_panel(
frame: &mut Frame,
area: Rect,
app: &App,
title: &str,
cards: &[crate::tui::widgets::card_grid::GridCard],
selected: usize,
border: ratatui::style::Color,
palette: &Palette
) {
use ratatui::{
style::{Modifier, Style},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Paragraph}
};
use crate::tui::{app::ResourceTab, widgets::card_grid};
let block = Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(border))
.title(Line::from(Span::styled(
title.to_string(),
Style::default()
.fg(palette.title)
.add_modifier(Modifier::BOLD)
)));
let inner = block.inner(area);
frame.render_widget(block, area);
if inner.height == 0 || inner.width < 6 {
return;
}
let focused = app.pane == Pane::Content;
let (header, grid) = crate::tui::widgets::service_header::layout(inner, ResourceTab::Projects);
if let Some(header_area) = header {
crate::tui::widgets::service_header::render(
frame,
header_area,
ResourceTab::Projects,
focused && app.content_on_create,
palette
);
}
if cards.is_empty() {
frame.render_widget(
Paragraph::new(Line::from(Span::styled(
t!("ui.drill_empty").to_string(),
Style::default().fg(palette.dim)
))),
grid
);
return;
}
let cols = card_grid::columns(grid.width, card_grid::longest_title(cards));
let shown = if app.content_on_create {
usize::MAX
} else {
selected
};
card_grid::render_grid_in(frame, grid, cards, shown, cols, palette);
}
fn render_drill(
frame: &mut Frame,
area: Rect,
app: &App,
view: &DrillView,
selected: usize,
border: ratatui::style::Color,
palette: &Palette
) {
use crate::tui::widgets::card_grid::GridCard;
let cards: Vec<GridCard> = view
.items
.iter()
.map(|item| {
let mut card = GridCard::new(item.name.clone());
if !item.detail.is_empty() {
let (color, _) =
crate::tui::widgets::resource_list::status_view(&item.detail, palette);
card = card.status(color, item.detail.clone());
}
card.meta(item.kind.clone())
})
.collect();
let title = format!(" {} ({}) ", view.title, view.items.len());
render_project_panel(frame, area, app, &title, &cards, selected, border, palette);
}