Skip to main content

blizz_ui/
wizard_composer.rs

1//! Composites question text, selector or text-entry chrome, hint, timer bar,
2//! and optional cursor positioning for the blizz setup wizard prompt layer.
3//!
4//! Layout is driven by a [`LayoutStack`] — each component declares its
5//! alignment intent and the stack handles vertical positioning.
6
7use std::io::{self, Write};
8
9use crate::Renderer;
10use crate::components::hint::HintComponent;
11use crate::components::selector::SelectorComponent;
12use crate::components::text::TextComponent;
13use crate::components::text_entry::TextEntryComponent;
14use crate::components::timer_bar::TimerBarComponent;
15use crate::layout_stack::{Align, LayoutStack};
16use crate::prompt;
17
18/// References to wizard prompt widgets for [`render_prompt_layer`].
19#[derive(Clone, Copy)]
20pub struct WizardPromptRefs<'a> {
21  pub question: &'a TextComponent,
22  pub entry: &'a TextEntryComponent,
23  pub selector: &'a SelectorComponent,
24  pub hint: &'a HintComponent,
25  pub timer_bar: &'a TimerBarComponent,
26}
27
28#[cfg(not(tarpaulin_include))]
29pub fn render_prompt_layer<W: Write>(
30  renderer: &mut Renderer<W>,
31  refs: WizardPromptRefs<'_>,
32) -> io::Result<()> {
33  let terminal_width = renderer.ctx().terminal_size.width;
34  let layout = match refs.entry.layout() {
35    Some(l) => l,
36    None => return Ok(()),
37  };
38
39  let mut stack = LayoutStack::new(
40    layout.question_row,
41    terminal_width,
42    layout.box_column,
43    layout.inner_width,
44  );
45
46  let question_width = refs.question.text.chars().count() as u16;
47  stack.draw(renderer, refs.question, Align::Content(question_width))?;
48
49  if refs.selector.visible {
50    stack.draw(renderer, refs.selector, Align::Full)?;
51    stack.skip(1);
52    return draw_footer(&mut stack, renderer, refs.hint, refs.timer_bar);
53  }
54
55  if refs.entry.visible && refs.entry.open > 0.0 {
56    stack.draw(renderer, refs.entry, Align::Box)?;
57  } else {
58    let box_height = layout.hint_row.saturating_sub(layout.box_top_row);
59    stack.skip(box_height);
60  }
61
62  draw_footer(&mut stack, renderer, refs.hint, refs.timer_bar)?;
63
64  if refs.entry.is_fully_ready()
65    && let Some(col) = refs.entry.cursor_column(terminal_width)
66  {
67    prompt::queue_cursor(&mut renderer.writer, layout, col)?;
68  }
69
70  Ok(())
71}
72
73#[cfg(not(tarpaulin_include))]
74fn draw_footer<W: Write>(
75  stack: &mut LayoutStack,
76  renderer: &mut Renderer<W>,
77  hint: &HintComponent,
78  timer_bar: &TimerBarComponent,
79) -> io::Result<()> {
80  let hint_width = hint.text.chars().count() as u16;
81  stack.draw(renderer, hint, Align::Content(hint_width))?;
82
83  let seg = TimerBarComponent::segment_count(timer_bar.progress, stack.terminal_width());
84  stack.draw(renderer, timer_bar, Align::Content(seg as u16))?;
85  Ok(())
86}