blizz_ui/components/
text_entry.rs1use std::io::Write;
2
3use rand::Rng;
4
5use crate::decode;
6use crate::input_buffer::{self, InputBuffer};
7use crate::layout as layout_mod;
8use crate::prompt::{
9 TextEntry, TextEntryLayout, queue_clear_prompt, queue_text_entry, queue_text_entry_with_cursor,
10 text_entry, text_entry_layout,
11};
12
13#[derive(Debug, Clone)]
14pub struct TextEntryComponent {
15 pub label: String,
16 pub content: String,
17 pub open: f64,
18 pub label_reveal: f64,
19 pub content_reveal: f64,
20 pub visible: bool,
21}
22
23impl TextEntryComponent {
24 pub fn prompt(label: impl Into<String>, default_value: impl Into<String>) -> Self {
26 Self {
27 label: label.into(),
28 content: default_value.into(),
29 open: 0.0,
30 label_reveal: 0.0,
31 content_reveal: 0.0,
32 visible: true,
33 }
34 }
35
36 pub fn hidden() -> Self {
38 Self {
39 label: String::new(),
40 content: String::new(),
41 open: 0.0,
42 label_reveal: 0.0,
43 content_reveal: 0.0,
44 visible: false,
45 }
46 }
47
48 #[cfg(not(tarpaulin_include))]
49 pub fn render_with_cursor<W: Write, R: Rng>(
50 &self,
51 writer: &mut W,
52 layout: &TextEntryLayout,
53 tw: u16,
54 buf: &InputBuffer,
55 rng: &mut R,
56 ) -> std::io::Result<u16> {
57 if self.open < 1.0 {
58 self.render_partial_open(writer, layout, tw)?;
59 return Ok(0);
60 }
61
62 let label_display = if self.label_reveal >= 1.0 {
63 self.label.clone()
64 } else {
65 let revealed = (self.label.chars().count() as f64 * self.label_reveal).round() as usize;
66 decode::decode_frame(&self.label, revealed, rng)
67 };
68
69 let content_display = if self.content_reveal >= 1.0 {
70 self.content.clone()
71 } else {
72 let revealed = (self.content.chars().count() as f64 * self.content_reveal).round() as usize;
73 decode::decode_frame(&self.content, revealed, rng)
74 };
75
76 let frame = text_entry(&label_display, &content_display, tw);
77 let label_width = label_display.chars().count() as u16;
78 let buf_text = input_buffer::text(buf);
79 let cursor_pos = input_buffer::cursor(buf);
80 let selection = input_buffer::selection_range(buf);
81 queue_text_entry_with_cursor(
82 writer,
83 layout,
84 &frame,
85 buf_text,
86 label_width,
87 cursor_pos,
88 selection,
89 )
90 }
91
92 #[cfg(not(tarpaulin_include))]
93 pub fn render_partial_open<W: Write>(
94 &self,
95 writer: &mut W,
96 layout: &TextEntryLayout,
97 tw: u16,
98 ) -> std::io::Result<()> {
99 let full_width = layout.inner_width;
100 let raw = ((full_width as f64) * self.open).round() as u16;
101 let even = (raw / 2) * 2;
102 let current_width = if even == 0 && raw > 0 {
103 2
104 } else {
105 even.min(full_width)
106 };
107
108 if current_width == 0 {
109 return queue_clear_prompt(writer, layout);
110 }
111
112 let partial = TextEntry {
113 label: String::new(),
114 hint: String::new(),
115 inner_width: current_width,
116 };
117 let partial_layout =
118 text_entry_layout(&partial, layout_mod::size(tw, layout.question_row + 10));
119 let merged = TextEntryLayout {
120 question_row: layout.question_row,
121 box_top_row: layout.box_top_row,
122 content_row: layout.content_row,
123 box_bottom_row: layout.box_bottom_row,
124 hint_row: layout.hint_row,
125 box_column: partial_layout.box_column,
126 inner_width: current_width,
127 };
128 queue_text_entry(writer, &merged, &partial, "", 0)
129 }
130}