use crate::{builder::validator::textfieldinfo::TextFieldInfo, prelude::*}; use anyhow::Result; use utilities::prelude::*; use std::sync::{ atomic::{AtomicBool, Ordering::SeqCst}, Arc, RwLock, }; use super::fill_type::{FillType, InnerFillType}; pub struct TextFieldBuilder { background: Option, text_color: Color, text: Option, text_ratio: f32, text_alignment: TextAlignment, } impl TextFieldBuilder { pub fn set_background(mut self, background: impl Into) -> Self { self.background = Some(background.into()); self } pub fn set_text_color(mut self, text_color: Color) -> Self { self.text_color = text_color; self } pub fn set_text_alignment(mut self, text_alignment: TextAlignment) -> Self { self.text_alignment = text_alignment; self } pub fn set_text(mut self, text: impl Into) -> Self { self.text = Some(text.into()); self } pub fn set_text_ratio(mut self, ratio: f32) -> Self { self.text_ratio = ratio; self } pub fn build(self, gui_handler: Arc) -> Result> { let framable = Framable::new(gui_handler.clone(), false)?; let background = RwLock::new( self.background .clone() .map(|info| FillType::new(framable.clone(), info)) .transpose()?, ); let textable = Textable::new( framable.clone(), match self.text { Some(text) => text, None => "".to_string(), }, self.text_ratio, self.text_alignment, self.text_color, )?; let text_changed_executable = Executable::new(); let writeable = Writeable::new( gui_handler, textable.clone(), text_changed_executable.clone(), )?; let text_field = Arc::new(TextField { framable, background, writeable, textable, text_changed_executable, visible: AtomicBool::new(false), }); text_field.update_color_change(); Ok(text_field) } } pub struct TextField { framable: Arc, writeable: Arc, textable: Arc, text_changed_executable: Arc>>, background: RwLock>, visible: AtomicBool, } impl TextField { pub fn builder() -> TextFieldBuilder { TextFieldBuilder { background: None, text_color: Color::Black, text: None, text_ratio: 0.7, text_alignment: TextAlignment::default(), } } pub fn try_from(info: &TextFieldInfo, gui_handler: &Arc) -> Result> { let text = info.text.read().unwrap().clone(); let color = info.text_color; let mut text_field_builder = Self::builder() .set_text_color(color) .set_text_alignment(info.text_alignment); if let Some(background_type) = &info.background_type { text_field_builder = text_field_builder.set_background(background_type.clone()); } if let Some(ratio) = info.text_ratio { text_field_builder = text_field_builder.set_text_ratio(ratio); } if !text.is_empty() { text_field_builder = text_field_builder.set_text(text); } text_field_builder.build(gui_handler.clone()) } fn update_color_change(self: &Arc) { if let Some(background) = &*self.background.read().unwrap() { 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 { if let Some(background) = &*me.background.read().unwrap() { if let InnerFillType::Color(colorable) = &background.inner { colorable.set_color(focus_color)?; } } } else if let Some(background) = &*me.background.read().unwrap() { if let InnerFillType::Color(colorable) = &background.inner { colorable.set_color(normal_color)?; } } } Ok(()) }); } } } pub fn set_text_changed_callback(&self, f: F) where F: Fn(Option) -> Result<()> + Send + Sync + 'static, { self.text_changed_executable.set_callback(f); } pub fn set_text_color(&self, text_color: Color) -> Result<()> { self.textable.set_text_color(text_color) } pub fn set_text(&self, text: impl ToString) -> Result<()> { self.textable.set_text(text) } pub fn text(&self) -> Option { let t = self.textable.text(); if t.is_empty() { None } else { Some(t) } } pub fn set_background(self: &Arc, background: impl Into) -> Result<()> { super::set_background(self.visible(), &self.framable, &self.background, background)?; self.update_color_change(); Ok(()) } pub fn background_color(&self) -> Option { if let Some(background) = self.background.read().unwrap().as_ref() { if let InnerFillType::Color(colorable) = &background.inner { return Some(colorable.color()); } } None } pub fn add_letter(&self, letter: char) -> Result<()> { self.writeable.add_letter(letter) } pub fn remove_last(&self) -> Result<()> { self.writeable.remove_last() } pub fn focus_input(&self) -> Result<()> { self.writeable.set_active() } fn disable_base(&self) -> Result<()> { self.framable.delete()?; self.textable.delete()?; self.writeable.delete()?; if let Some(background) = self.background.read().unwrap().as_ref() { background.disable()?; } Ok(()) } } impl GuiElementTraits for TextField { fn gridable(&self) -> Option<&dyn Gridable> { Some(self) } fn visibility(&self) -> Option<&dyn Visibility> { Some(self) } fn downcast<'a>(&'a self) -> Option> { Some(GuiElement::TextField(self)) } } impl Gridable for TextField { fn set_frame( &self, x: i32, y: i32, w: u32, h: u32, vert_align: VerticalAlign, hori_align: HorizontalAlign, ) -> Result<()> { self.framable.set_frame(x, y, w, h, vert_align, hori_align); self.textable.update_text()?; if let Some(background) = self.background.read().unwrap().as_ref() { background.update_frame()?; } Ok(()) } fn selectable(&self) -> Option<&Arc> { None } fn type_name(&self) -> &str { "Text Field" } fn set_layer(&self, layer: i32) -> Result<()> { assert!(!self.visible(), "can't change ui layer while visible"); self.framable.set_ui_layer(layer); self.textable.set_ui_layer(layer); self.writeable.set_ui_layer(layer); if let Some(background) = self.background.read().unwrap().as_ref() { background.set_ui_layer(layer); } Ok(()) } } impl Visibility for TextField { fn visible(&self) -> bool { self.visible.load(SeqCst) } fn set_visibility(&self, visibility: bool) -> Result<()> { if visibility != self.visible() { self.visible.store(visibility, SeqCst); if visibility { self.framable.add()?; self.textable.add()?; self.writeable.add()?; if let Some(background) = self.background.read().unwrap().as_ref() { background.enable()?; } } else { self.disable_base()?; } } Ok(()) } } impl Drop for TextField { fn drop(&mut self) { if self.visible() { self.disable_base().unwrap(); } self.textable.clear_callback(); } }