Render a single question page with options
(frame: &mut Frame, prompt: &QuestionPrompt, theme: &Theme, area: Rect)
| 446 | |
| 447 | /// Render a single question page with options |
| 448 | fn render_question_page(frame: &mut Frame, prompt: &QuestionPrompt, theme: &Theme, area: Rect) { |
| 449 | let q = match prompt.current_question() { |
| 450 | Some(q) => q, |
| 451 | None => return, |
| 452 | }; |
| 453 | |
| 454 | // Calculate overlay height: header(1) + question(2) + options + other + hint(2) + padding |
| 455 | let options_count = q.options.len() + 1; // +1 for "Other" |
| 456 | let description_lines: usize = q |
| 457 | .options |
| 458 | .iter() |
| 459 | .map(|o| if o.description.is_empty() { 0 } else { 1 }) |
| 460 | .sum(); |
| 461 | let tab_line = if prompt.tab_count() > 1 { 2 } else { 0 }; |
| 462 | let editing_line = if prompt.editing_custom { 1 } else { 0 }; |
| 463 | let content_height = 2 + tab_line + options_count + description_lines + editing_line + 1; // question + options + descriptions + padding |
| 464 | let overlay_height = (content_height as u16 + 3).min(area.height.saturating_sub(2)); // +3 for borders/hint |
| 465 | |
| 466 | let overlay_area = Rect { |
| 467 | x: area.x, |
| 468 | y: area.y + area.height.saturating_sub(overlay_height), |
| 469 | width: area.width, |
| 470 | height: overlay_height, |
| 471 | }; |
| 472 | |
| 473 | frame.render_widget(Clear, overlay_area); |
| 474 | |
| 475 | let chunks = Layout::default() |
| 476 | .direction(Direction::Vertical) |
| 477 | .constraints([ |
| 478 | Constraint::Min(3), // content |
| 479 | Constraint::Length(2), // hint bar |
| 480 | ]) |
| 481 | .split(overlay_area); |
| 482 | |
| 483 | // Content block with accent left border |
| 484 | let content_block = Block::default() |
| 485 | .borders(Borders::LEFT | Borders::TOP | Borders::RIGHT) |
| 486 | .border_style(Style::default().fg(theme.primary)) |
| 487 | .style(Style::default().bg(theme.background_panel)); |
| 488 | |
| 489 | let inner = content_block.inner(chunks[0]); |
| 490 | frame.render_widget(content_block, chunks[0]); |
| 491 | |
| 492 | let mut lines: Vec<Line> = Vec::new(); |
| 493 | |
| 494 | // Tab bar (multi-question only) |
| 495 | if prompt.tab_count() > 1 { |
| 496 | let mut tab_spans = Vec::new(); |
| 497 | for (i, qd) in prompt.questions.iter().enumerate() { |
| 498 | if i > 0 { |
| 499 | tab_spans.push(Span::raw(" ")); |
| 500 | } |
| 501 | let is_active = i == prompt.current_tab; |
| 502 | let is_answered = !prompt.answers[i].is_empty(); |
| 503 | if is_active { |
| 504 | tab_spans.push(Span::styled( |
| 505 | format!(" {} ", qd.header), |
no test coverage detected