-
Ygg01 authoredUnverified68d56a11
text_box.rs 5.15 KiB
use crate::{core::{
render_command::RenderCommand,
rsx,
styles::{Corner, Style, Units},
widget, Bound, Children, Color, EventType, MutableBound, OnEvent, WidgetProps,
}, widgets::ChangeEvent};
use kayak_core::{CursorIcon, OnLayout};
use crate::widgets::{Background, Clip, Text, OnChange};
/// Props used by the [`TextBox`] widget
#[derive(Default, Debug, PartialEq, Clone)]
pub struct TextBoxProps {
/// If true, prevents the widget from being focused (and consequently edited)
pub disabled: bool,
/// A callback for when the text value was changed
pub on_change: Option<OnChange>,
/// The text to display when the user input is empty
pub placeholder: Option<String>,
/// The user input
///
/// This is a controlled state. You _must_ set this to the value to you wish to be displayed.
/// You can use the [`on_change`] callback to update this prop as the user types.
pub value: String,
pub styles: Option<Style>,
pub children: Option<Children>,
pub on_event: Option<OnEvent>,
pub on_layout: Option<OnLayout>,
pub focusable: Option<bool>,
}
impl WidgetProps for TextBoxProps {
fn get_children(&self) -> Option<Children> {
self.children.clone()
}
fn set_children(&mut self, children: Option<Children>) {
self.children = children;
}
fn get_styles(&self) -> Option<Style> {
self.styles.clone()
}
fn get_on_event(&self) -> Option<OnEvent> {
self.on_event.clone()
}
fn get_on_layout(&self) -> Option<OnLayout> {
self.on_layout.clone()
}
fn get_focusable(&self) -> Option<bool> {
Some(!self.disabled)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Focus(pub bool);
#[widget]
/// A widget that displays a text input field
///
/// # Props
///
/// __Type:__ [`TextBoxProps`]
///
/// | Common Prop | Accepted |
/// | :---------: | :------: |
/// | `children` | ✅ |
/// | `styles` | ✅ |
/// | `on_event` | ✅ |
/// | `on_layout` | ✅ |
/// | `focusable` | ✅ |
///
pub fn TextBox(props: TextBoxProps) {
let TextBoxProps {
on_change,
placeholder,
value,
..
} = props.clone();
props.styles = Some(
Style::default()
// Required styles
.with_style(Style {
render_command: RenderCommand::Layout.into(),
..Default::default()
})
// Apply any prop-given styles
.with_style(&props.styles)
// If not set by props, apply these styles
.with_style(Style {
top: Units::Pixels(0.0).into(),
bottom: Units::Pixels(0.0).into(),
height: Units::Pixels(26.0).into(),
cursor: CursorIcon::Text.into(),
..Default::default()
}),
);
let background_styles = Style {
background_color: Color::new(0.176, 0.196, 0.215, 1.0).into(),
border_radius: Corner::all(5.0).into(),
height: Units::Pixels(26.0).into(),
padding_left: Units::Pixels(5.0).into(),
padding_right: Units::Pixels(5.0).into(),
..Default::default()
};
let has_focus = context.create_state(Focus(false)).unwrap();
let mut current_value = value.clone();
let cloned_on_change = on_change.clone();
let cloned_has_focus = has_focus.clone();
props.on_event = Some(OnEvent::new(move |_, event| match event.event_type {
EventType::CharInput { c } => {
if !cloned_has_focus.get().0 {
return;
}
if is_backspace(c) {
if !current_value.is_empty() {
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(),
});
}
}
}
EventType::Focus => cloned_has_focus.set(Focus(true)),
EventType::Blur => cloned_has_focus.set(Focus(false)),
_ => {}
}));
let text_styles = if value.is_empty() || (has_focus.get().0 && value.is_empty()) {
Style {
color: Color::new(0.5, 0.5, 0.5, 1.0).into(),
..Style::default()
}
} else {
Style::default()
};
let value = if value.is_empty() {
placeholder.unwrap_or_else(|| value.clone())
} else {
value
};
rsx! {
<Background styles={Some(background_styles)}>
<Clip>
<Text
content={value}
size={14.0}
line_height={Some(22.0)}
styles={Some(text_styles)}
/>
</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}'
}