Skip to content
Snippets Groups Projects
text_box.rs 4.1 KiB
Newer Older
StarArawn's avatar
StarArawn committed
    rsx,
    styles::{Style, StyleProp, Units},
    widget, Bound, Color, EventType, MutableBound, OnEvent,
};
use std::sync::{Arc, RwLock};

use crate::widgets::{Background, Clip, Text};
StarArawn's avatar
StarArawn committed

#[derive(Debug, Clone, PartialEq)]
pub struct ChangeEvent {
    pub value: String,
}

#[derive(Clone)]
pub struct OnChange(pub Arc<RwLock<dyn FnMut(ChangeEvent) + Send + Sync + 'static>>);

impl OnChange {
    pub fn new<F: FnMut(ChangeEvent) + Send + Sync + 'static>(f: F) -> OnChange {
        OnChange(Arc::new(RwLock::new(f)))
    }
}

impl PartialEq for OnChange {
    fn eq(&self, _other: &Self) -> bool {
        true
    }
}

impl std::fmt::Debug for OnChange {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_tuple("OnChange").finish()
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Focus(pub bool);

#[widget(focusable)]
StarArawn's avatar
StarArawn committed
pub fn TextBox(value: String, on_change: Option<OnChange>, placeholder: Option<String>) {
    let current_styles = styles.clone().unwrap_or_default();
    *styles = Some(Style {
        render_command: StyleProp::Value(RenderCommand::Layout),
StarArawn's avatar
StarArawn committed
        height: StyleProp::Value(Units::Pixels(26.0)),
        top: if matches!(current_styles.top, StyleProp::Value { .. }) {
            current_styles.top.clone()
        } else {
            StyleProp::Value(Units::Pixels(0.0))
        },
        bottom: if matches!(current_styles.bottom, StyleProp::Value { .. }) {
            current_styles.top.clone()
        } else {
            StyleProp::Value(Units::Pixels(0.0))
        },
        ..current_styles
StarArawn's avatar
StarArawn committed
    let background_styles = Style {
        background_color: StyleProp::Value(Color::new(0.176, 0.196, 0.215, 1.0)),
        border_radius: StyleProp::Value((5.0, 5.0, 5.0, 5.0)),
        height: StyleProp::Value(Units::Pixels(26.0)),
        padding_left: StyleProp::Value(Units::Pixels(5.0)),
        padding_right: StyleProp::Value(Units::Pixels(5.0)),
        ..styles.clone().unwrap_or_default()
    };

    let has_focus = context.create_state(Focus(false)).unwrap();
StarArawn's avatar
StarArawn committed

StarArawn's avatar
StarArawn committed
    let mut current_value = value.clone();
StarArawn's avatar
StarArawn committed
    let cloned_on_change = on_change.clone();
    let cloned_has_focus = has_focus.clone();
StarArawn's avatar
StarArawn committed
    self.on_event = Some(OnEvent::new(move |_, event| match event.event_type {
        EventType::CharInput { c } => {
            if is_backspace(c) {
                if !current_value.is_empty() {
StarArawn's avatar
StarArawn committed
                    current_value.truncate(current_value.len() - 1);
                }
            } else if !c.is_control() {
                current_value.push(c);
            }
            if let Some(on_change) = cloned_on_change.as_ref() {
                if let Ok(mut on_change) = on_change.0.write() {
                    on_change(ChangeEvent {
                        value: current_value.clone(),
                    });
                }
            }
        }
StarArawn's avatar
StarArawn committed
        EventType::Focus => cloned_has_focus.set(Focus(true)),
        EventType::Blur => cloned_has_focus.set(Focus(false)),
StarArawn's avatar
StarArawn committed
        _ => {}
    }));

    let text_styles = if value.is_empty() || (has_focus.get().0 && value.is_empty()) {
        Style {
IceSentry's avatar
IceSentry committed
            color: StyleProp::Value(Color::new(0.5, 0.5, 0.5, 1.0)),
            ..Style::default()
        }
    } else {
        Style {
            color: styles.clone().unwrap_or_default().color,
            ..Style::default()
        }
    };

StarArawn's avatar
StarArawn committed
    let value = if value.is_empty() {
        placeholder.unwrap_or_else(|| value.clone())
StarArawn's avatar
StarArawn committed
    } else {
StarArawn's avatar
StarArawn committed
    };
StarArawn's avatar
StarArawn committed
    rsx! {
        <Background styles={Some(background_styles)}>
            <Clip>
                <Text
                    content={value}
                    size={14.0}
StarArawn's avatar
StarArawn committed
                    line_height={Some(22.0)}
                    styles={Some(text_styles)}
                />
StarArawn's avatar
StarArawn committed
            </Clip>
        </Background>
    }
}

/// Checks if the given character contains the "Backspace" sequence
///
/// Context: [Wikipedia](https://en.wikipedia.org/wiki/Backspace#Common_use)
fn is_backspace(c: char) -> bool {
    c == '\u{8}' || c == '\u{7f}'