diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index e75f5a6a..1a46a27b 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -1,5 +1,5 @@ use gpui::{ - actions, AppContext, ClickEvent, FocusHandle, InteractiveElement, IntoElement, KeyBinding, + actions, AppContext, FocusHandle, InteractiveElement, IntoElement, KeyBinding, ParentElement as _, Render, SharedString, Styled, View, ViewContext, VisualContext, WindowContext, }; @@ -7,7 +7,7 @@ use gpui::{ use ui::{ button::Button, h_flex, - input::{InputOtp, TextEvent, TextInput}, + input::{InputEvent, InputOtp, TextInput}, prelude::FluentBuilder as _, v_flex, Clickable, FocusableCycle, IconName, Size, }; @@ -86,16 +86,15 @@ impl InputStory { .placeholder("This input have prefix and suffix.") }); - let view = cx.view().clone(); - let otp_input = cx.new_view(|cx| { - InputOtp::new(6, cx) - .masked(true) - .on_change(move |value: &SharedString, cx| { - view.update(cx, |view, _| { - view.otp_value = Some(value.clone()); - }) - }) - }); + let otp_input = cx.new_view(|cx| InputOtp::new(6, cx).masked(true)); + cx.subscribe(&otp_input, |this, _, ev: &InputEvent, cx| match ev { + InputEvent::Change(text) => { + this.otp_value = Some(text.clone()); + cx.notify(); + } + _ => {} + }) + .detach(); Self { input1, @@ -126,11 +125,6 @@ impl InputStory { } } - #[allow(unused)] - fn on_change(ev: &ClickEvent, cx: &mut WindowContext) { - println!("Input changed: {:?}", ev); - } - fn tab(&mut self, _: &Tab, cx: &mut ViewContext) { self.cycle_focus(true, cx); } @@ -142,14 +136,14 @@ impl InputStory { fn on_input_event( &mut self, _: View, - event: &TextEvent, + event: &InputEvent, _cx: &mut ViewContext, ) { match event { - TextEvent::Input { text } => println!("Input: {}", text), - TextEvent::PressEnter => println!("PressEnter"), - TextEvent::Focus => println!("Focus"), - TextEvent::Blur => println!("Blur"), + InputEvent::Change(text) => println!("Change: {}", text), + InputEvent::PressEnter => println!("PressEnter"), + InputEvent::Focus => println!("Focus"), + InputEvent::Blur => println!("Blur"), }; } } @@ -183,25 +177,35 @@ impl Render for InputStory { .justify_start() .gap_3() .child( - section("Normal Input", cx) - .child(self.input1.clone()) - .child(self.input2.clone()), + h_flex() + .gap_3() + .items_start() + .child( + section("Normal Input", cx) + .child(self.input1.clone()) + .child(self.input2.clone()), + ) + .child( + section("Input State", cx) + .child(self.disabled_input.clone()) + .child(self.mash_input.clone()), + ), ) .child( - section("Input State", cx) - .child(self.disabled_input.clone()) - .child(self.mash_input.clone()), - ) - .child( - section("Preifx and Suffix", cx) - .child(self.prefix_input1.clone()) - .child(self.both_input1.clone()) - .child(self.suffix_input1.clone()), - ) - .child( - section("Input Size", cx) - .child(self.large_input.clone()) - .child(self.small_input.clone()), + h_flex() + .gap_3() + .items_start() + .child( + section("Preifx and Suffix", cx) + .child(self.prefix_input1.clone()) + .child(self.both_input1.clone()) + .child(self.suffix_input1.clone()), + ) + .child( + section("Input Size", cx) + .child(self.large_input.clone()) + .child(self.small_input.clone()), + ), ) .child( section("Input OTP", cx).child( diff --git a/crates/story/src/progress_story.rs b/crates/story/src/progress_story.rs index d3ce3768..e8b57706 100644 --- a/crates/story/src/progress_story.rs +++ b/crates/story/src/progress_story.rs @@ -3,8 +3,13 @@ use gpui::{ WindowContext, }; use ui::{ - button::Button, divider::Divider, h_flex, indicator::Indicator, progress::Progress, - slider::Slider, v_flex, Clickable, IconName, Size, + button::Button, + divider::Divider, + h_flex, + indicator::Indicator, + progress::Progress, + slider::{Slider, SliderEvent}, + v_flex, Clickable, IconName, Size, }; pub struct ProgressStory { @@ -21,31 +26,36 @@ impl ProgressStory { } fn new(cx: &mut ViewContext) -> Self { - let slider1 = Slider::horizontal() - .min(-255.) - .max(255.) - .default_value(15.) - .step(15.) - .on_change(cx.listener(|this, value, cx| { + let slider1 = cx.new_view(|_| { + Slider::horizontal() + .min(-255.) + .max(255.) + .default_value(15.) + .step(15.) + }); + cx.subscribe(&slider1, |this, _, event: &SliderEvent, cx| match event { + SliderEvent::Change(value) => { this.slider1_value = *value; cx.notify(); - })); + } + }) + .detach(); - let slider2 = Slider::horizontal() - .min(0.) - .max(5.) - .step(1.0) - .on_change(cx.listener(|this, value, cx| { + let slider2 = cx.new_view(|_| Slider::horizontal().min(0.).max(5.).step(1.0)); + cx.subscribe(&slider2, |this, _, event: &SliderEvent, cx| match event { + SliderEvent::Change(value) => { this.slider2_value = *value; cx.notify(); - })); + } + }) + .detach(); Self { value: 50., slider1_value: 15., slider2_value: 1., - slider1: cx.new_view(|_| slider1), - slider2: cx.new_view(|_| slider2), + slider1, + slider2, } } diff --git a/crates/story/src/webview_story.rs b/crates/story/src/webview_story.rs index 407bef55..1f82cc1a 100644 --- a/crates/story/src/webview_story.rs +++ b/crates/story/src/webview_story.rs @@ -5,7 +5,7 @@ use gpui::{ use ui::{ button::Button, h_flex, - input::{TextEvent, TextInput}, + input::{InputEvent, TextInput}, theme::ActiveTheme, v_flex, webview::WebView, @@ -44,8 +44,8 @@ impl WebViewStory { cx.subscribe( &address_input, - |this: &mut Self, input, event: &TextEvent, cx| match event { - TextEvent::PressEnter => { + |this: &mut Self, input, event: &InputEvent, cx| match event { + InputEvent::PressEnter => { let url = input.read(cx).text(); this.webview.update(cx, |view, _| { view.load_url(&url); diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 657c85b7..92303fc2 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -47,8 +47,8 @@ actions!( ] ); -pub enum TextEvent { - Input { text: SharedString }, +pub enum InputEvent { + Change(SharedString), PressEnter, Focus, Blur, @@ -113,7 +113,7 @@ pub struct TextInput { size: Size, } -impl EventEmitter for TextInput {} +impl EventEmitter for TextInput {} impl TextInput { pub fn new(cx: &mut ViewContext) -> Self { @@ -292,7 +292,7 @@ impl TextInput { } fn enter(&mut self, _: &Enter, cx: &mut ViewContext) { - cx.emit(TextEvent::PressEnter); + cx.emit(InputEvent::PressEnter); } fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext) { @@ -505,7 +505,7 @@ impl TextInput { self.blink_cursor.update(cx, |cursor, cx| { cursor.start(cx); }); - cx.emit(TextEvent::Focus); + cx.emit(InputEvent::Focus); } fn on_blur(&mut self, cx: &mut ViewContext) { @@ -513,7 +513,7 @@ impl TextInput { self.blink_cursor.update(cx, |cursor, cx| { cursor.stop(cx); }); - cx.emit(TextEvent::Blur); + cx.emit(InputEvent::Blur); } fn pause_blink_cursor(&mut self, cx: &mut ViewContext) { @@ -571,9 +571,7 @@ impl ViewInputHandler for TextInput { (self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into(); self.selected_range = range.start + new_text.len()..range.start + new_text.len(); self.marked_range.take(); - cx.emit(TextEvent::Input { - text: self.text.clone(), - }); + cx.emit(InputEvent::Change(self.text.clone())); cx.notify(); } @@ -602,9 +600,7 @@ impl ViewInputHandler for TextInput { .map(|range_utf16| self.range_from_utf16(range_utf16)) .map(|new_range| new_range.start + range.start..new_range.end + range.end) .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len()); - cx.emit(TextEvent::Input { - text: self.text.clone(), - }); + cx.emit(InputEvent::Change(self.text.clone())); cx.notify(); } diff --git a/crates/ui/src/input/input_otp.rs b/crates/ui/src/input/input_otp.rs index f59d6c80..f9f877e3 100644 --- a/crates/ui/src/input/input_otp.rs +++ b/crates/ui/src/input/input_otp.rs @@ -1,12 +1,17 @@ use gpui::{ - div, prelude::FluentBuilder as _, AnyElement, Context, FocusHandle, FocusableView, - InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, MouseDownEvent, - ParentElement as _, Render, SharedString, Styled as _, ViewContext, WindowContext, + div, prelude::FluentBuilder as _, AnyElement, Context, EventEmitter, FocusHandle, + FocusableView, InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, + MouseDownEvent, ParentElement as _, Render, SharedString, Styled as _, ViewContext, }; use crate::{h_flex, theme::ActiveTheme, v_flex}; -use super::blink_cursor::BlinkCursor; +use super::{blink_cursor::BlinkCursor, InputEvent}; + +pub enum InputOptEvent { + /// When all OTP input have filled, this event will be triggered. + Change(SharedString), +} pub struct InputOtp { focus_handle: FocusHandle, @@ -15,7 +20,6 @@ pub struct InputOtp { masked: bool, value: SharedString, blink_cursor: Model, - on_change: Option>, } impl InputOtp { @@ -28,7 +32,6 @@ impl InputOtp { number_of_groups: 2, value: SharedString::default(), masked: false, - on_change: None, blink_cursor: blink_cursor.clone(), }; @@ -65,15 +68,6 @@ impl InputOtp { self } - /// Set callback to be called when the value of the OTP Input changes (All OTP input have filled). - pub fn on_change(mut self, f: F) -> Self - where - F: Fn(&SharedString, &mut WindowContext) + 'static, - { - self.on_change = Some(Box::new(f)); - self - } - fn on_input_mouse_down(&mut self, _: &MouseDownEvent, cx: &mut ViewContext) { cx.focus(&self.focus_handle); } @@ -108,9 +102,7 @@ impl InputOtp { self.value = SharedString::from(chars.iter().collect::()); if self.value.chars().count() == self.length { - if let Some(on_change) = &self.on_change { - on_change(&self.value, cx); - } + cx.emit(InputEvent::Change(self.value.clone())); } cx.notify() } @@ -119,12 +111,14 @@ impl InputOtp { self.blink_cursor.update(cx, |cursor, cx| { cursor.start(cx); }); + cx.emit(InputEvent::Focus); } fn on_blur(&mut self, cx: &mut ViewContext) { self.blink_cursor.update(cx, |cursor, cx| { cursor.stop(cx); }); + cx.emit(InputEvent::Blur); } fn pause_blink_cursor(&mut self, cx: &mut ViewContext) { @@ -139,6 +133,7 @@ impl FocusableView for InputOtp { self.focus_handle.clone() } } +impl EventEmitter for InputOtp {} impl Render for InputOtp { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { diff --git a/crates/ui/src/list/list.rs b/crates/ui/src/list/list.rs index 33ffc498..63f283bd 100644 --- a/crates/ui/src/list/list.rs +++ b/crates/ui/src/list/list.rs @@ -2,7 +2,7 @@ use std::{cell::Cell, rc::Rc}; use gpui::prelude::FluentBuilder as _; -use crate::input::{TextEvent, TextInput}; +use crate::input::{InputEvent, TextInput}; use crate::scroll::ScrollbarState; use crate::theme::{ActiveTheme, Colorize as _}; use crate::{scroll::Scrollbar, v_flex}; @@ -157,15 +157,15 @@ where fn on_query_input_event( &mut self, _: View, - event: &TextEvent, + event: &InputEvent, cx: &mut ViewContext, ) { match event { - TextEvent::Input { text } => { + InputEvent::Change(text) => { self.delegate.perform_search(&text.trim(), cx); cx.notify() } - TextEvent::PressEnter => self.action_confirm(&Confirm, cx), + InputEvent::PressEnter => self.action_confirm(&Confirm, cx), _ => {} } } diff --git a/crates/ui/src/slider.rs b/crates/ui/src/slider.rs index 359066f2..f2b47357 100644 --- a/crates/ui/src/slider.rs +++ b/crates/ui/src/slider.rs @@ -3,14 +3,18 @@ use crate::{ tooltip::Tooltip, }; use gpui::{ - canvas, div, px, relative, Axis, Bounds, DragMoveEvent, EntityId, InteractiveElement, - IntoElement, MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, Render, - StatefulInteractiveElement as _, Styled, ViewContext, VisualContext as _, WindowContext, + canvas, div, px, relative, Axis, Bounds, DragMoveEvent, EntityId, EventEmitter, + InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement as _, Pixels, + Point, Render, StatefulInteractiveElement as _, Styled, ViewContext, VisualContext as _, }; #[derive(Clone, Render)] pub struct DragThumb(EntityId); +pub enum SliderEvent { + Change(f32), +} + /// A slider component. pub struct Slider { axis: Axis, @@ -18,7 +22,6 @@ pub struct Slider { max: f32, step: f32, value: f32, - on_change: Option>, bounds: Bounds, } @@ -30,7 +33,6 @@ impl Slider { max: 100.0, step: 1.0, value: 0.0, - on_change: None, bounds: Bounds::default(), } } @@ -63,12 +65,6 @@ impl Slider { self } - /// Set the on_change callback of the slider. - pub fn on_change(mut self, on_change: impl Fn(&f32, &mut WindowContext) + 'static) -> Self { - self.on_change = Some(Box::new(on_change)); - self - } - /// Set the value of the slider. pub fn set_value(&mut self, value: f32, cx: &mut gpui::ViewContext) { self.value = value; @@ -114,9 +110,7 @@ impl Slider { let value = (value / step).round() * step; self.value = value.clamp(self.min, self.max); - if let Some(on_change) = &self.on_change { - on_change(&self.value, cx); - } + cx.emit(SliderEvent::Change(self.value)); cx.notify(); } @@ -160,6 +154,8 @@ impl Slider { } } +impl EventEmitter for Slider {} + impl Render for Slider { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { div()