use gpui::{ div, App, AppContext as _, Context, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render, Styled, Subscription, Window, }; use crate::{section, Tab, TabPrev}; use gpui_component::{ button::{Button, ButtonVariants as _}, input::{InputEvent, InputState, MaskPattern, TextInput}, v_flex, ContextModal, FocusableCycle, Icon, IconName, Sizable, }; const CONTEXT: &str = "InputStory"; pub fn init(cx: &mut App) { cx.bind_keys([ KeyBinding::new("shift-tab", TabPrev, Some(CONTEXT)), KeyBinding::new("tab", Tab, Some(CONTEXT)), ]) } pub struct InputStory { input1: Entity, input2: Entity, input_esc: Entity, mask_input: Entity, disabled_input: Entity, prefix_input1: Entity, suffix_input1: Entity, both_input1: Entity, large_input: Entity, small_input: Entity, phone_input: Entity, mask_input2: Entity, currency_input: Entity, _subscriptions: Vec, } impl super::Story for InputStory { fn title() -> &'static str { "Input" } fn closable() -> bool { false } fn new_view(window: &mut Window, cx: &mut App) -> Entity { Self::view(window, cx) } } impl InputStory { pub fn view(window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| Self::new(window, cx)) } fn new(window: &mut Window, cx: &mut Context) -> Self { let input1 = cx.new(|cx| { InputState::new(window, cx) .default_value("Hello 世界,this is GPUI component, this is a long text.") }); let input2 = cx.new(|cx| InputState::new(window, cx).placeholder("Enter text here...")); let input_esc = cx.new(|cx| { InputState::new(window, cx) .placeholder("Enter text and clear it by pressing ESC") .clean_on_escape() }); let mask_input = cx.new(|cx| { InputState::new(window, cx) .masked(true) .default_value("this-is-password") }); let prefix_input1 = cx.new(|cx| InputState::new(window, cx).placeholder("Search some thing...")); let suffix_input1 = cx.new(|cx| { InputState::new(window, cx) .placeholder("This input only support [a-zA-Z0-9] characters.") .pattern(regex::Regex::new(r"^[a-zA-Z0-9]*$").unwrap()) }); let both_input1 = cx.new(|cx| { InputState::new(window, cx).placeholder("This input have prefix and suffix.") }); let phone_input = cx.new(|cx| InputState::new(window, cx).mask_pattern("(999)-999-9999")); let mask_input2 = cx.new(|cx| InputState::new(window, cx).mask_pattern("AAA-###-AAA")); let currency_input = cx.new(|cx| { InputState::new(window, cx).mask_pattern(MaskPattern::Number { separator: Some(','), fraction: Some(3), }) }); let _subscriptions = vec![ cx.subscribe_in(&input1, window, Self::on_input_event), cx.subscribe_in(&input2, window, Self::on_input_event), cx.subscribe_in(&phone_input, window, Self::on_input_event), ]; Self { input1, input2, input_esc, mask_input, disabled_input: cx .new(|cx| InputState::new(window, cx).default_value("This is disabled input")), large_input: cx.new(|cx| InputState::new(window, cx).placeholder("Large input")), small_input: cx.new(|cx| { InputState::new(window, cx) .validate(|s| s.parse::().is_ok()) .placeholder("validate to limit float number.") }), prefix_input1, suffix_input1, both_input1, phone_input, mask_input2, currency_input, _subscriptions, } } fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context) { self.cycle_focus(true, window, cx); } fn tab_prev(&mut self, _: &TabPrev, window: &mut Window, cx: &mut Context) { self.cycle_focus(false, window, cx); } fn on_input_event( &mut self, _: &Entity, event: &InputEvent, _window: &mut Window, _cx: &mut Context, ) { match event { InputEvent::Change(text) => println!("Change: {}", text), InputEvent::PressEnter { secondary } => println!("PressEnter secondary: {}", secondary), InputEvent::Focus => println!("Focus"), InputEvent::Blur => println!("Blur"), }; } } impl FocusableCycle for InputStory { fn cycle_focus_handles(&self, _: &mut Window, cx: &mut App) -> Vec { [ self.input1.focus_handle(cx), self.input2.focus_handle(cx), self.disabled_input.focus_handle(cx), self.mask_input.focus_handle(cx), self.prefix_input1.focus_handle(cx), self.both_input1.focus_handle(cx), self.suffix_input1.focus_handle(cx), self.currency_input.focus_handle(cx), self.phone_input.focus_handle(cx), self.mask_input2.focus_handle(cx), self.large_input.focus_handle(cx), self.small_input.focus_handle(cx), self.input_esc.focus_handle(cx), ] .to_vec() } } impl Focusable for InputStory { fn focus_handle(&self, cx: &gpui::App) -> gpui::FocusHandle { self.input1.focus_handle(cx) } } impl Render for InputStory { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { v_flex() .key_context(CONTEXT) .id("input-story") .on_action(cx.listener(Self::tab)) .on_action(cx.listener(Self::tab_prev)) .size_full() .justify_start() .gap_3() .child( section("Normal Input") .max_w_md() .child(TextInput::new(&self.input1).cleanable()) .child(TextInput::new(&self.input2)), ) .child( section("Input State") .max_w_md() .child(TextInput::new(&self.disabled_input).disabled(true)) .child(TextInput::new(&self.mask_input).mask_toggle().cleanable()), ) .child( section("Prefix and Suffix") .max_w_md() .child( TextInput::new(&self.prefix_input1) .cleanable() .prefix(Icon::new(IconName::Search).small()), ) .child( TextInput::new(&self.both_input1) .cleanable() .prefix(div().child(Icon::new(IconName::Search).small())) .suffix(Button::new("info").ghost().icon(IconName::Info).xsmall()), ) .child( TextInput::new(&self.suffix_input1) .cleanable() .suffix(Button::new("info").ghost().icon(IconName::Info).xsmall()), ), ) .child( section("Currency Input with thousands separator") .max_w_md() .child(TextInput::new(&self.currency_input)) .child( div().child(format!("Value: {:?}", self.currency_input.read(cx).value())), ), ) .child( section("Input with mask pattern: (999)-999-9999") .max_w_md() .child(TextInput::new(&self.phone_input)) .child( v_flex() .child(format!("Value: {:?}", self.phone_input.read(cx).value())) .child(format!( "Unmask Value: {:?}", self.phone_input.read(cx).unmask_value() )), ), ) .child( section("Input with mask pattern: AAA-###-AAA") .max_w_md() .child(TextInput::new(&self.mask_input2)) .child( v_flex() .child(format!("Value: {:?}", self.mask_input2.read(cx).value())) .child(format!( "Unmask Value: {:?}", self.mask_input2.read(cx).unmask_value() )), ), ) .child( section("Input Size") .max_w_md() .child(TextInput::new(&self.large_input).large().cleanable()) .child(TextInput::new(&self.small_input).small().cleanable()), ) .child( section("Cleanable and ESC to clean") .max_w_md() .child(TextInput::new(&self.input_esc).cleanable()), ) .child( section("Focused Input") .max_w_md() .whitespace_normal() .overflow_hidden() .child(div().child(format!( "Value: {:?}", window.focused_input(cx).map(|input| input.read(cx).value()) ))), ) } }