blizz-ui 3.0.0-dev.11

Self-rendering terminal UI components for the blizz wizard
Documentation
//! Composites question text, selector or text-entry chrome, hint, timer bar,
//! and optional cursor positioning for the blizz setup wizard prompt layer.
//!
//! The wizard crate owns UI state (`UiController`); this module only lays out
//! and draws those pieces in a fixed order.

use std::io::{self, Write};

use rand::Rng;

use crate::components::hint::HintComponent;
use crate::components::selector::SelectorComponent;
use crate::components::text::TextComponent;
use crate::components::text_entry::TextEntryComponent;
use crate::components::timer_bar::TimerBarComponent;
use crate::input_buffer::InputBuffer;
use crate::layout::Size;
use crate::prompt::{self, text_entry as text_entry_dims, text_entry_layout};

/// References to wizard prompt widgets for [`render_wizard_prompt_layer`].
#[derive(Clone, Copy)]
pub struct WizardPromptRefs<'a> {
  pub question: &'a TextComponent,
  pub entry: &'a TextEntryComponent,
  pub selector: &'a SelectorComponent,
  pub hint: &'a HintComponent,
  pub timer_bar: &'a TimerBarComponent,
}

#[cfg(not(tarpaulin_include))]
pub fn render_wizard_prompt_layer<W: Write, R: Rng>(
  writer: &mut W,
  terminal_size: Size,
  refs: WizardPromptRefs<'_>,
  input_buf: &InputBuffer,
  rng: &mut R,
) -> io::Result<()> {
  let tw = terminal_size.width;
  let dims = text_entry_dims(&refs.entry.label, &refs.entry.content, tw);
  let layout = text_entry_layout(&dims, terminal_size);

  refs.question.render(writer, tw, layout.question_row, rng)?;

  if refs.selector.visible {
    let selector_start = layout.box_top_row;
    let visible_count = refs.selector.render(writer, tw, selector_start, rng)?;
    let hint_row = selector_start
      .saturating_add(visible_count as u16)
      .saturating_add(1);
    refs.hint.render(writer, tw, hint_row)?;
    refs
      .timer_bar
      .render(writer, tw, hint_row.saturating_add(1))?;
    return Ok(());
  }

  let cursor_col = if refs.entry.visible && refs.entry.open > 0.0 {
    Some(
      refs
        .entry
        .render_with_cursor(writer, &layout, tw, input_buf, rng)?,
    )
  } else {
    None
  };

  refs.hint.render(writer, tw, layout.hint_row)?;
  refs
    .timer_bar
    .render(writer, tw, layout.hint_row.saturating_add(1))?;

  let box_fully_ready = refs.entry.visible
    && refs.entry.open >= 1.0
    && refs.entry.label_reveal >= 1.0
    && refs.entry.content_reveal >= 1.0;

  if box_fully_ready && let Some(col) = cursor_col {
    prompt::queue_cursor(writer, &layout, col)?;
  }

  Ok(())
}