use ratatui::{
Frame,
layout::{Constraint, Layout},
};
use crate::tui::popup::{
ActionSelectPopup, ConfirmPermissionsPopup, ConfirmQuitPopup, ErrorPopup, IssueItem,
IssueLoadingPopup, PopupSizing, SessionItem, SessionTerminatedPopup, WorkspacePopup,
};
use crate::tui::status_bar::StatusBar;
use crate::tui::tabs::{StatusIndicator, TabBar, TabItem};
use crate::tui::terminal::TerminalView;
use super::state::AppState;
use super::tuiapp::TuiApp;
impl TuiApp {
const SLOT_TAB_BAR: usize = 0;
const SLOT_TERMINAL: usize = 1;
const SLOT_STATUS_BAR: usize = 2;
pub fn render(&mut self, frame: &mut Frame) {
let area = frame.area();
let areas = Layout::vertical([
Constraint::Length(1), Constraint::Min(1), Constraint::Length(1), ])
.split(area);
self.render_tab_bar(frame, areas[Self::SLOT_TAB_BAR]);
self.render_terminal(frame, areas[Self::SLOT_TERMINAL]);
self.render_status_bar(frame, areas[Self::SLOT_STATUS_BAR]);
self.render_popup(frame, area);
}
fn render_tab_bar(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
let tabs: Vec<TabItem> = self
.sessions
.iter()
.map(|s| TabItem::new(&s.name, StatusIndicator::from(&s.status)))
.collect();
frame.render_widget(TabBar::new(&tabs, self.active_idx), area);
}
fn render_terminal(&mut self, frame: &mut Frame, area: ratatui::layout::Rect) {
if self.sessions.is_empty() {
frame.render_widget(crate::tui::welcome::WelcomeScreen::new(), area);
return;
}
let Some(session) = self.sessions.get(self.active_idx) else {
return;
};
let offset = self.scroll_offsets.get(&session.id).copied().unwrap_or(0);
if let Some(parser) = self.terminal_buffers.get_mut(&session.id) {
parser.set_scrollback(offset);
frame.render_widget(TerminalView::new(parser), area);
parser.set_scrollback(0);
}
}
fn render_status_bar(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
let mut status_bar =
StatusBar::new(self.sessions.len()).notifications(self.pending_count());
if let Some(cost) = self.today_cost {
status_bar = status_bar.today_cost(cost);
}
if let Some(session) = self.sessions.get(self.active_idx) {
status_bar = status_bar.active_session(&session.name);
if let Some(branch) = &session.branch {
status_bar = status_bar.active_branch(branch);
}
}
frame.render_widget(status_bar, area);
}
fn render_popup(&mut self, frame: &mut Frame, area: ratatui::layout::Rect) {
match self.state {
AppState::WorkspacePopup => self.render_workspace_popup(frame, area),
AppState::ConfirmQuit => self.render_confirm_quit(frame, area),
AppState::ErrorPopup { ref message, .. } => {
let popup = ErrorPopup::new(message);
let popup_area = popup.compute_area(area);
frame.render_widget(popup, popup_area);
}
AppState::SessionTerminatedPopup {
session_id,
exit_code,
} => {
self.render_terminated_popup(frame, area, session_id, exit_code);
}
AppState::IssueLoading {
issue_number,
ref phase,
} => {
let popup = IssueLoadingPopup::new(issue_number, phase.message());
let popup_area = popup.compute_area(area);
frame.render_stateful_widget(popup, popup_area, &mut self.issue_loading_throbber);
}
AppState::ActionSelectPopup {
issue_number,
ref choices,
} => {
let popup = ActionSelectPopup::new(issue_number, choices);
let popup_area = popup.compute_area(area);
frame.render_stateful_widget(popup, popup_area, &mut self.action_select_state);
}
AppState::ConfirmPermissions {
ref branch,
selected_yes,
..
} => {
let popup = if selected_yes {
ConfirmPermissionsPopup::new(branch).select_yes()
} else {
ConfirmPermissionsPopup::new(branch).select_no()
};
let popup_area = popup.compute_area(area);
frame.render_widget(popup, popup_area);
}
AppState::Normal => {}
}
}
fn render_workspace_popup(&mut self, frame: &mut Frame, area: ratatui::layout::Rect) {
let session_items: Vec<SessionItem> = self
.sessions
.iter()
.map(|s| {
SessionItem::new(&s.name, StatusIndicator::from(&s.status))
.branch(s.branch.clone())
.pending(self.is_pending(&s.id))
})
.collect();
let issues: Vec<_> = self
.issues
.iter()
.map(|i| IssueItem::new(i.number, &i.title, &i.labels))
.collect();
let worktrees = self.worktrees.clone();
let input = self.popup_state.input.clone();
let cursor = self.popup_state.cursor;
let section = self.popup_state.section;
let popup =
WorkspacePopup::new(&session_items, &worktrees, &issues, &input, cursor, section);
let popup_area = popup.compute_area(area);
frame.render_stateful_widget(popup, popup_area, &mut self.popup_state);
}
fn render_confirm_quit(&self, frame: &mut Frame, area: ratatui::layout::Rect) {
let popup = if self.quit_selected_yes {
ConfirmQuitPopup::new().select_yes()
} else {
ConfirmQuitPopup::new()
};
let popup_area = popup.compute_area(area);
frame.render_widget(popup, popup_area);
}
fn render_terminated_popup(
&self,
frame: &mut Frame,
area: ratatui::layout::Rect,
session_id: crate::session::SessionId,
exit_code: Option<i32>,
) {
let session_name = self
.sessions
.iter()
.find(|s| s.id == session_id)
.map_or("Session", |s| &s.name);
let popup = SessionTerminatedPopup::new(session_name, exit_code);
let popup_area = popup.compute_area(area);
frame.render_widget(popup, popup_area);
}
}