use crate::{ builder::validator::multi_line_text_field_info::MultiLineTextFieldInfo, guihandler::gui::writeable::ModifyText, prelude::*, }; use std::sync::{ atomic::{AtomicU32, Ordering::SeqCst}, Arc, Mutex, }; use anyhow::Result; use super::fill_type::InnerFillType; pub struct MultiLineTextFieldBuilder { text_alignment: TextAlignment, text_ratio: f32, text_color: Color, text: Option, line_count: u32, background: Option, } impl MultiLineTextFieldBuilder { pub fn set_text(mut self, text: impl Into) -> Self { self.text = Some(text.into()); self } pub fn set_ratio(mut self, ratio: f32) -> Self { self.text_ratio = ratio; self } pub fn set_text_color(mut self, text_color: impl Into) -> Self { self.text_color = text_color.into(); self } pub fn set_text_alignment(mut self, text_alignment: TextAlignment) -> Self { self.text_alignment = text_alignment; self } pub fn set_line_count(mut self, line_count: u32) -> Self { self.line_count = line_count; self } pub fn set_background(mut self, background: impl Into) -> Self { self.background = Some(background.into()); self } pub fn build(self, gui_handler: Arc) -> Result> { let base_grid = Grid::new(gui_handler.clone(), 1, self.line_count as usize, false)?; base_grid.set_margin(0); base_grid.set_padding(0); if let Some(background) = self.background { base_grid.set_background(background)?; } for i in 0..self.line_count { let label = Label::builder() .set_ratio(self.text_ratio) .set_text_color(self.text_color) .set_text_alignment(self.text_alignment) .build(gui_handler.clone())?; base_grid.attach(label, 0, i as usize, 1, 1)?; } let text = Arc::new(SplittedText::new(self.text)); let text_changed_exec = Executable::new(); let writeable = Writeable::new(gui_handler, text.clone(), text_changed_exec.clone())?; let multi_line_text_field = Arc::new(MultiLineTextField { grid: base_grid, text, text_changed_exec, writeable, }); multi_line_text_field.text_changed_exec.set_callback({ let weak_tf = Arc::downgrade(&multi_line_text_field); move |_text| { if let Some(tf) = weak_tf.upgrade() { tf.update_text()?; } Ok(()) } }); multi_line_text_field.update_color_change(); Ok(multi_line_text_field) } } struct SplittedText { characters_per_line: AtomicU32, text: Mutex, splits: Mutex>, } impl SplittedText { fn new(text: Option) -> Self { Self { characters_per_line: AtomicU32::new(0), text: Mutex::new(text.unwrap_or_default()), splits: Mutex::new(Vec::new()), } } fn text(&self) -> String { self.text.lock().unwrap().clone() } fn set_characters_per_line(&self, characters_per_line: u32) { self.characters_per_line.store(characters_per_line, SeqCst); } fn lines(&self) -> Vec { self.splits.lock().unwrap().clone() } fn update_text(&self) -> Result<()> { let text = self.text.lock().unwrap(); let splits = text.split(' '); let character_count = self.characters_per_line.load(SeqCst) as usize; let mut current_line = String::new(); let mut lines = Vec::new(); for split in splits { let current_len = current_line.len(); let next_len = split.len() + 1; if (current_len + next_len) <= character_count { if current_line.is_empty() { current_line = split.to_string(); } else { current_line += &format!(" {}", split); } } else { lines.push(current_line); current_line = split.to_string(); } } if !current_line.is_empty() { lines.push(current_line); } *self.splits.lock().unwrap() = lines.clone(); Ok(()) } } impl ModifyText for SplittedText { fn set_text(&self, text: String) -> Result<()> { *self.text.lock().unwrap() = text; self.update_text() } fn add_letter(&self, letter: char) -> Result { *self.text.lock().unwrap() += &format!("{}", letter); self.update_text()?; Ok(self.text.lock().unwrap().clone()) } fn remove_last(&self) -> Result> { self.text.lock().unwrap().pop(); self.update_text()?; let text = self.text.lock().unwrap().clone(); Ok(if text.is_empty() { None } else { Some(text) }) } } pub struct MultiLineTextField { grid: Arc, text: Arc, text_changed_exec: Arc>>, writeable: Arc, } impl MultiLineTextField { pub fn builder() -> MultiLineTextFieldBuilder { MultiLineTextFieldBuilder { text_alignment: TextAlignment::Center, text_ratio: 0.7, text_color: Color::Black, text: None, line_count: 1, background: None, } } pub fn set_text(&self, text: impl ToString) -> Result<()> { self.text.set_text(text.to_string()) } fn update_text(&self) -> Result<()> { let lines = self.text.lines(); self.iter_label(|label, y| { if y < lines.len() { label.set_text(&lines[y])?; } else { label.set_text("")?; } Ok(()) }) } fn update_color_change(self: &Arc) { if let Some(background) = &*self.grid.background() { if let InnerFillType::Color(colorable) = &background.inner { let normal_color = colorable.color(); let focus_color = normal_color * 1.4; let weak_self = Arc::downgrade(self); self.writeable.set_activation_changed(move |active| { if let Some(me) = weak_self.upgrade() { if active { me.grid.set_background(focus_color)?; } else { me.grid.set_background(normal_color)?; } } Ok(()) }); } } } pub fn text(&self) -> String { self.text.text() } pub fn set_text_color(&self, text_color: Color) -> Result<()> { self.iter_label(|label, _| label.set_text_color(text_color)) } pub fn set_alignment(&self, alignment: TextAlignment) -> Result<()> { self.iter_label(|label, _| label.set_alignment(alignment)) } pub fn set_text_ratio(&self, ratio: f32) -> Result<()> { self.iter_label(|label, _| label.set_text_ratio(ratio)) } pub fn set_background(self: &Arc, background: impl Into) -> Result<()> { self.grid.set_background(background)?; self.update_color_change(); Ok(()) } pub fn focus_input(&self) -> Result<()> { self.writeable.set_active() } pub fn try_from( multi_line_text_field_info: &MultiLineTextFieldInfo, gui_handler: &Arc, ) -> Result> { let text = multi_line_text_field_info.text.read().unwrap().clone(); let color = multi_line_text_field_info.text_color; let mut multi_line_text_field_builder = MultiLineTextField::builder() .set_text_color(color) .set_text_alignment(multi_line_text_field_info.text_alignment) .set_line_count(multi_line_text_field_info.line_count.get()?); if let Some(background_type) = &multi_line_text_field_info.background_type { multi_line_text_field_builder = multi_line_text_field_builder.set_background(background_type.clone()); } if let Some(ratio) = multi_line_text_field_info.text_ratio { multi_line_text_field_builder = multi_line_text_field_builder.set_ratio(ratio); } if !text.is_empty() { multi_line_text_field_builder = multi_line_text_field_builder.set_text(text); } multi_line_text_field_builder.build(gui_handler.clone()) } fn iter_label(&self, f: F) -> Result<()> where F: Fn(&Label, usize) -> Result<()>, { for y in 0..self.grid.dimensions().1 { let child = self.grid.child_at(0, y)?.unwrap(); let gui_element = child.downcast().unwrap(); let label = gui_element.label().unwrap(); f(label, y)?; } Ok(()) } } impl GuiElementTraits for MultiLineTextField { fn gridable(&self) -> Option<&dyn Gridable> { Some(self) } fn visibility(&self) -> Option<&dyn Visibility> { Some(self) } fn downcast<'a>(&'a self) -> Option> { Some(GuiElement::MultiLineTextField(self)) } } impl Visibility for MultiLineTextField { fn visible(&self) -> bool { self.grid.visible() } fn set_visibility(&self, visibility: bool) -> Result<()> { if visibility != self.visible() { if visibility { self.writeable.add()?; } else { self.writeable.delete()?; } } self.grid.set_visibility(visibility) } } impl Gridable for MultiLineTextField { fn set_frame( &self, x: i32, y: i32, w: u32, h: u32, vert_align: VerticalAlign, hori_align: HorizontalAlign, ) -> Result<()> { self.grid.set_frame(x, y, w, h, vert_align, hori_align)?; if let Some(child) = self.grid.child_at(0, 0)? { let gui_element = child.downcast().unwrap(); let label = gui_element.label().unwrap(); let left = label.framable.left(); let right = label.framable.right(); let top = label.framable.top(); let bottom = label.framable.bottom(); let width = right - left; let height = bottom - top; let text_ratio = label.text_ratio(); let text_size = height as f32 * text_ratio; let max_letter_count = (width as f32 / (text_size / 2.0)).floor(); self.text.set_characters_per_line(max_letter_count as u32); self.update_text()?; } Ok(()) } fn selectable(&self) -> Option<&Arc> { None } fn type_name(&self) -> &str { "MultiLineLabel" } fn set_layer(&self, layer: i32) -> Result<()> { self.writeable.set_ui_layer(layer); self.grid.set_layer(layer) } } impl Drop for MultiLineTextField { fn drop(&mut self) { if self.visible() { self.writeable.delete().unwrap(); } } }