use std::io::Write;
use rand::Rng;
use crate::decode;
use crate::input_buffer::{self, InputBuffer};
use crate::layout as layout_mod;
use crate::prompt::{
TextEntry, TextEntryLayout, queue_clear_prompt, queue_text_entry, queue_text_entry_with_cursor,
text_entry, text_entry_layout,
};
#[derive(Debug, Clone)]
pub struct TextEntryComponent {
pub label: String,
pub content: String,
pub open: f64,
pub label_reveal: f64,
pub content_reveal: f64,
pub visible: bool,
}
impl TextEntryComponent {
pub fn prompt(label: impl Into<String>, default_value: impl Into<String>) -> Self {
Self {
label: label.into(),
content: default_value.into(),
open: 0.0,
label_reveal: 0.0,
content_reveal: 0.0,
visible: true,
}
}
pub fn hidden() -> Self {
Self {
label: String::new(),
content: String::new(),
open: 0.0,
label_reveal: 0.0,
content_reveal: 0.0,
visible: false,
}
}
pub fn tick_decode(&mut self, question_reveal: f64, box_open_speed: f64, reveal_speed: f64) {
if !self.visible || question_reveal < 0.5 {
return;
}
self.open = (self.open + box_open_speed).min(1.0);
if self.open >= 1.0 {
self.label_reveal = (self.label_reveal + reveal_speed).min(1.0);
self.content_reveal = (self.content_reveal + reveal_speed).min(1.0);
}
}
pub fn tick_transition_encode(
&mut self,
next_has_prompt_box: bool,
encode_speed: f64,
morph_speed: f64,
) {
if !self.visible {
return;
}
self.label_reveal = (self.label_reveal - encode_speed).max(0.0);
self.content_reveal = (self.content_reveal - encode_speed).max(0.0);
let text_hidden = self.label_reveal <= 0.0 && self.content_reveal <= 0.0;
if !next_has_prompt_box && text_hidden {
self.open = (self.open - morph_speed).max(0.0);
}
}
pub fn tick_transition_decode(&mut self, encode_speed: f64, morph_speed: f64) {
if !self.visible {
return;
}
if self.open < 1.0 {
self.open = (self.open + morph_speed).min(1.0);
} else {
self.label_reveal = (self.label_reveal + encode_speed).min(1.0);
self.content_reveal = (self.content_reveal + encode_speed).min(1.0);
}
}
pub fn tick_exit_close(&mut self, encode_speed: f64, box_close_speed: f64) {
if !self.visible {
return;
}
self.label_reveal = (self.label_reveal - encode_speed).max(0.0);
self.content_reveal = (self.content_reveal - encode_speed).max(0.0);
if self.label_reveal <= 0.0 && self.content_reveal <= 0.0 {
self.open = (self.open - box_close_speed).max(0.0);
}
}
#[cfg(not(tarpaulin_include))]
pub fn render_with_cursor<W: Write, R: Rng>(
&self,
writer: &mut W,
layout: &TextEntryLayout,
tw: u16,
buf: &InputBuffer,
rng: &mut R,
) -> std::io::Result<u16> {
if self.open < 1.0 {
self.render_partial_open(writer, layout, tw)?;
return Ok(0);
}
let label_display = if self.label_reveal >= 1.0 {
self.label.clone()
} else {
let revealed = (self.label.chars().count() as f64 * self.label_reveal).round() as usize;
decode::decode_frame(&self.label, revealed, rng)
};
let content_display = if self.content_reveal >= 1.0 {
self.content.clone()
} else {
let revealed = (self.content.chars().count() as f64 * self.content_reveal).round() as usize;
decode::decode_frame(&self.content, revealed, rng)
};
let frame = text_entry(&label_display, &content_display, tw);
let label_width = label_display.chars().count() as u16;
let buf_text = input_buffer::text(buf);
let cursor_pos = input_buffer::cursor(buf);
let selection = input_buffer::selection_range(buf);
queue_text_entry_with_cursor(
writer,
layout,
&frame,
buf_text,
label_width,
cursor_pos,
selection,
)
}
#[cfg(not(tarpaulin_include))]
pub fn render_partial_open<W: Write>(
&self,
writer: &mut W,
layout: &TextEntryLayout,
tw: u16,
) -> std::io::Result<()> {
let full_width = layout.inner_width;
let raw = ((full_width as f64) * self.open).round() as u16;
let even = (raw / 2) * 2;
let current_width = if even == 0 && raw > 0 {
2
} else {
even.min(full_width)
};
if current_width == 0 {
return queue_clear_prompt(writer, layout);
}
let partial = TextEntry {
label: String::new(),
hint: String::new(),
inner_width: current_width,
};
let partial_layout =
text_entry_layout(&partial, layout_mod::size(tw, layout.question_row + 10));
let merged = TextEntryLayout {
question_row: layout.question_row,
box_top_row: layout.box_top_row,
content_row: layout.content_row,
box_bottom_row: layout.box_bottom_row,
hint_row: layout.hint_row,
box_column: partial_layout.box_column,
inner_width: current_width,
};
queue_text_entry(writer, &merged, &partial, "", 0)
}
}