ui/src/elements/textfield.rs

332 lines
8.6 KiB
Rust

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<FillTypeInfo>,
text_color: Color,
text: Option<String>,
text_ratio: f32,
text_alignment: TextAlignment,
}
impl TextFieldBuilder {
pub fn set_background(mut self, background: impl Into<FillTypeInfo>) -> 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<String>) -> 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<GuiHandler>) -> Result<Arc<TextField>> {
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<Framable>,
writeable: Arc<Writeable>,
textable: Arc<Textable>,
text_changed_executable: Arc<Executable<Option<String>>>,
background: RwLock<Option<FillType>>,
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<GuiHandler>) -> Result<Arc<Self>> {
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<Self>) {
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<F>(&self, f: F)
where
F: Fn(Option<String>) -> 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<String> {
let t = self.textable.text();
if t.is_empty() {
None
} else {
Some(t)
}
}
pub fn set_background(self: &Arc<Self>, background: impl Into<FillTypeInfo>) -> Result<()> {
super::set_background(self.visible(), &self.framable, &self.background, background)?;
self.update_color_change();
Ok(())
}
pub fn background_color(&self) -> Option<Color> {
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<GuiElement<'a>> {
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<Selectable>> {
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();
}
}