Refactor Slider, Input, InputOtp to use EventEmiter to emit change (#45)

This commit is contained in:
Jason Lee 2024-07-18 18:38:58 +08:00 committed by GitHub
parent f6146220e4
commit bac5392e0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 108 additions and 107 deletions

View file

@ -1,5 +1,5 @@
use gpui::{ use gpui::{
actions, AppContext, ClickEvent, FocusHandle, InteractiveElement, IntoElement, KeyBinding, actions, AppContext, FocusHandle, InteractiveElement, IntoElement, KeyBinding,
ParentElement as _, Render, SharedString, Styled, View, ViewContext, VisualContext, ParentElement as _, Render, SharedString, Styled, View, ViewContext, VisualContext,
WindowContext, WindowContext,
}; };
@ -7,7 +7,7 @@ use gpui::{
use ui::{ use ui::{
button::Button, button::Button,
h_flex, h_flex,
input::{InputOtp, TextEvent, TextInput}, input::{InputEvent, InputOtp, TextInput},
prelude::FluentBuilder as _, prelude::FluentBuilder as _,
v_flex, Clickable, FocusableCycle, IconName, Size, v_flex, Clickable, FocusableCycle, IconName, Size,
}; };
@ -86,16 +86,15 @@ impl InputStory {
.placeholder("This input have prefix and suffix.") .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));
let otp_input = cx.new_view(|cx| { cx.subscribe(&otp_input, |this, _, ev: &InputEvent, cx| match ev {
InputOtp::new(6, cx) InputEvent::Change(text) => {
.masked(true) this.otp_value = Some(text.clone());
.on_change(move |value: &SharedString, cx| { cx.notify();
view.update(cx, |view, _| { }
view.otp_value = Some(value.clone()); _ => {}
}) })
}) .detach();
});
Self { Self {
input1, 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>) { fn tab(&mut self, _: &Tab, cx: &mut ViewContext<Self>) {
self.cycle_focus(true, cx); self.cycle_focus(true, cx);
} }
@ -142,14 +136,14 @@ impl InputStory {
fn on_input_event( fn on_input_event(
&mut self, &mut self,
_: View<TextInput>, _: View<TextInput>,
event: &TextEvent, event: &InputEvent,
_cx: &mut ViewContext<Self>, _cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
TextEvent::Input { text } => println!("Input: {}", text), InputEvent::Change(text) => println!("Change: {}", text),
TextEvent::PressEnter => println!("PressEnter"), InputEvent::PressEnter => println!("PressEnter"),
TextEvent::Focus => println!("Focus"), InputEvent::Focus => println!("Focus"),
TextEvent::Blur => println!("Blur"), InputEvent::Blur => println!("Blur"),
}; };
} }
} }
@ -183,25 +177,35 @@ impl Render for InputStory {
.justify_start() .justify_start()
.gap_3() .gap_3()
.child( .child(
section("Normal Input", cx) h_flex()
.child(self.input1.clone()) .gap_3()
.child(self.input2.clone()), .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( .child(
section("Input State", cx) h_flex()
.child(self.disabled_input.clone()) .gap_3()
.child(self.mash_input.clone()), .items_start()
) .child(
.child( section("Preifx and Suffix", cx)
section("Preifx and Suffix", cx) .child(self.prefix_input1.clone())
.child(self.prefix_input1.clone()) .child(self.both_input1.clone())
.child(self.both_input1.clone()) .child(self.suffix_input1.clone()),
.child(self.suffix_input1.clone()), )
) .child(
.child( section("Input Size", cx)
section("Input Size", cx) .child(self.large_input.clone())
.child(self.large_input.clone()) .child(self.small_input.clone()),
.child(self.small_input.clone()), ),
) )
.child( .child(
section("Input OTP", cx).child( section("Input OTP", cx).child(

View file

@ -3,8 +3,13 @@ use gpui::{
WindowContext, WindowContext,
}; };
use ui::{ use ui::{
button::Button, divider::Divider, h_flex, indicator::Indicator, progress::Progress, button::Button,
slider::Slider, v_flex, Clickable, IconName, Size, divider::Divider,
h_flex,
indicator::Indicator,
progress::Progress,
slider::{Slider, SliderEvent},
v_flex, Clickable, IconName, Size,
}; };
pub struct ProgressStory { pub struct ProgressStory {
@ -21,31 +26,36 @@ impl ProgressStory {
} }
fn new(cx: &mut ViewContext<Self>) -> Self { fn new(cx: &mut ViewContext<Self>) -> Self {
let slider1 = Slider::horizontal() let slider1 = cx.new_view(|_| {
.min(-255.) Slider::horizontal()
.max(255.) .min(-255.)
.default_value(15.) .max(255.)
.step(15.) .default_value(15.)
.on_change(cx.listener(|this, value, cx| { .step(15.)
});
cx.subscribe(&slider1, |this, _, event: &SliderEvent, cx| match event {
SliderEvent::Change(value) => {
this.slider1_value = *value; this.slider1_value = *value;
cx.notify(); cx.notify();
})); }
})
.detach();
let slider2 = Slider::horizontal() let slider2 = cx.new_view(|_| Slider::horizontal().min(0.).max(5.).step(1.0));
.min(0.) cx.subscribe(&slider2, |this, _, event: &SliderEvent, cx| match event {
.max(5.) SliderEvent::Change(value) => {
.step(1.0)
.on_change(cx.listener(|this, value, cx| {
this.slider2_value = *value; this.slider2_value = *value;
cx.notify(); cx.notify();
})); }
})
.detach();
Self { Self {
value: 50., value: 50.,
slider1_value: 15., slider1_value: 15.,
slider2_value: 1., slider2_value: 1.,
slider1: cx.new_view(|_| slider1), slider1,
slider2: cx.new_view(|_| slider2), slider2,
} }
} }

View file

@ -5,7 +5,7 @@ use gpui::{
use ui::{ use ui::{
button::Button, button::Button,
h_flex, h_flex,
input::{TextEvent, TextInput}, input::{InputEvent, TextInput},
theme::ActiveTheme, theme::ActiveTheme,
v_flex, v_flex,
webview::WebView, webview::WebView,
@ -44,8 +44,8 @@ impl WebViewStory {
cx.subscribe( cx.subscribe(
&address_input, &address_input,
|this: &mut Self, input, event: &TextEvent, cx| match event { |this: &mut Self, input, event: &InputEvent, cx| match event {
TextEvent::PressEnter => { InputEvent::PressEnter => {
let url = input.read(cx).text(); let url = input.read(cx).text();
this.webview.update(cx, |view, _| { this.webview.update(cx, |view, _| {
view.load_url(&url); view.load_url(&url);

View file

@ -47,8 +47,8 @@ actions!(
] ]
); );
pub enum TextEvent { pub enum InputEvent {
Input { text: SharedString }, Change(SharedString),
PressEnter, PressEnter,
Focus, Focus,
Blur, Blur,
@ -113,7 +113,7 @@ pub struct TextInput {
size: Size, size: Size,
} }
impl EventEmitter<TextEvent> for TextInput {} impl EventEmitter<InputEvent> for TextInput {}
impl TextInput { impl TextInput {
pub fn new(cx: &mut ViewContext<Self>) -> Self { pub fn new(cx: &mut ViewContext<Self>) -> Self {
@ -292,7 +292,7 @@ impl TextInput {
} }
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) { fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
cx.emit(TextEvent::PressEnter); cx.emit(InputEvent::PressEnter);
} }
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) { fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
@ -505,7 +505,7 @@ impl TextInput {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.start(cx); cursor.start(cx);
}); });
cx.emit(TextEvent::Focus); cx.emit(InputEvent::Focus);
} }
fn on_blur(&mut self, cx: &mut ViewContext<Self>) { fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
@ -513,7 +513,7 @@ impl TextInput {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.stop(cx); cursor.stop(cx);
}); });
cx.emit(TextEvent::Blur); cx.emit(InputEvent::Blur);
} }
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) { fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) {
@ -571,9 +571,7 @@ impl ViewInputHandler for TextInput {
(self.text[0..range.start].to_owned() + new_text + &self.text[range.end..]).into(); (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.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take(); self.marked_range.take();
cx.emit(TextEvent::Input { cx.emit(InputEvent::Change(self.text.clone()));
text: self.text.clone(),
});
cx.notify(); cx.notify();
} }
@ -602,9 +600,7 @@ impl ViewInputHandler for TextInput {
.map(|range_utf16| self.range_from_utf16(range_utf16)) .map(|range_utf16| self.range_from_utf16(range_utf16))
.map(|new_range| new_range.start + range.start..new_range.end + range.end) .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()); .unwrap_or_else(|| range.start + new_text.len()..range.start + new_text.len());
cx.emit(TextEvent::Input { cx.emit(InputEvent::Change(self.text.clone()));
text: self.text.clone(),
});
cx.notify(); cx.notify();
} }

View file

@ -1,12 +1,17 @@
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, AnyElement, Context, FocusHandle, FocusableView, div, prelude::FluentBuilder as _, AnyElement, Context, EventEmitter, FocusHandle,
InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton, MouseDownEvent, FocusableView, InteractiveElement, IntoElement, KeyDownEvent, Model, MouseButton,
ParentElement as _, Render, SharedString, Styled as _, ViewContext, WindowContext, MouseDownEvent, ParentElement as _, Render, SharedString, Styled as _, ViewContext,
}; };
use crate::{h_flex, theme::ActiveTheme, v_flex}; 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 { pub struct InputOtp {
focus_handle: FocusHandle, focus_handle: FocusHandle,
@ -15,7 +20,6 @@ pub struct InputOtp {
masked: bool, masked: bool,
value: SharedString, value: SharedString,
blink_cursor: Model<BlinkCursor>, blink_cursor: Model<BlinkCursor>,
on_change: Option<Box<dyn Fn(&SharedString, &mut WindowContext) + 'static>>,
} }
impl InputOtp { impl InputOtp {
@ -28,7 +32,6 @@ impl InputOtp {
number_of_groups: 2, number_of_groups: 2,
value: SharedString::default(), value: SharedString::default(),
masked: false, masked: false,
on_change: None,
blink_cursor: blink_cursor.clone(), blink_cursor: blink_cursor.clone(),
}; };
@ -65,15 +68,6 @@ impl InputOtp {
self self
} }
/// Set callback to be called when the value of the OTP Input changes (All OTP input have filled).
pub fn on_change<F>(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<Self>) { fn on_input_mouse_down(&mut self, _: &MouseDownEvent, cx: &mut ViewContext<Self>) {
cx.focus(&self.focus_handle); cx.focus(&self.focus_handle);
} }
@ -108,9 +102,7 @@ impl InputOtp {
self.value = SharedString::from(chars.iter().collect::<String>()); self.value = SharedString::from(chars.iter().collect::<String>());
if self.value.chars().count() == self.length { if self.value.chars().count() == self.length {
if let Some(on_change) = &self.on_change { cx.emit(InputEvent::Change(self.value.clone()));
on_change(&self.value, cx);
}
} }
cx.notify() cx.notify()
} }
@ -119,12 +111,14 @@ impl InputOtp {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.start(cx); cursor.start(cx);
}); });
cx.emit(InputEvent::Focus);
} }
fn on_blur(&mut self, cx: &mut ViewContext<Self>) { fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
self.blink_cursor.update(cx, |cursor, cx| { self.blink_cursor.update(cx, |cursor, cx| {
cursor.stop(cx); cursor.stop(cx);
}); });
cx.emit(InputEvent::Blur);
} }
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) { fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) {
@ -139,6 +133,7 @@ impl FocusableView for InputOtp {
self.focus_handle.clone() self.focus_handle.clone()
} }
} }
impl EventEmitter<InputEvent> for InputOtp {}
impl Render for InputOtp { impl Render for InputOtp {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {

View file

@ -2,7 +2,7 @@ use std::{cell::Cell, rc::Rc};
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use crate::input::{TextEvent, TextInput}; use crate::input::{InputEvent, TextInput};
use crate::scroll::ScrollbarState; use crate::scroll::ScrollbarState;
use crate::theme::{ActiveTheme, Colorize as _}; use crate::theme::{ActiveTheme, Colorize as _};
use crate::{scroll::Scrollbar, v_flex}; use crate::{scroll::Scrollbar, v_flex};
@ -157,15 +157,15 @@ where
fn on_query_input_event( fn on_query_input_event(
&mut self, &mut self,
_: View<TextInput>, _: View<TextInput>,
event: &TextEvent, event: &InputEvent,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
match event { match event {
TextEvent::Input { text } => { InputEvent::Change(text) => {
self.delegate.perform_search(&text.trim(), cx); self.delegate.perform_search(&text.trim(), cx);
cx.notify() cx.notify()
} }
TextEvent::PressEnter => self.action_confirm(&Confirm, cx), InputEvent::PressEnter => self.action_confirm(&Confirm, cx),
_ => {} _ => {}
} }
} }

View file

@ -3,14 +3,18 @@ use crate::{
tooltip::Tooltip, tooltip::Tooltip,
}; };
use gpui::{ use gpui::{
canvas, div, px, relative, Axis, Bounds, DragMoveEvent, EntityId, InteractiveElement, canvas, div, px, relative, Axis, Bounds, DragMoveEvent, EntityId, EventEmitter,
IntoElement, MouseButton, MouseDownEvent, ParentElement as _, Pixels, Point, Render, InteractiveElement, IntoElement, MouseButton, MouseDownEvent, ParentElement as _, Pixels,
StatefulInteractiveElement as _, Styled, ViewContext, VisualContext as _, WindowContext, Point, Render, StatefulInteractiveElement as _, Styled, ViewContext, VisualContext as _,
}; };
#[derive(Clone, Render)] #[derive(Clone, Render)]
pub struct DragThumb(EntityId); pub struct DragThumb(EntityId);
pub enum SliderEvent {
Change(f32),
}
/// A slider component. /// A slider component.
pub struct Slider { pub struct Slider {
axis: Axis, axis: Axis,
@ -18,7 +22,6 @@ pub struct Slider {
max: f32, max: f32,
step: f32, step: f32,
value: f32, value: f32,
on_change: Option<Box<dyn Fn(&f32, &mut WindowContext) + 'static>>,
bounds: Bounds<Pixels>, bounds: Bounds<Pixels>,
} }
@ -30,7 +33,6 @@ impl Slider {
max: 100.0, max: 100.0,
step: 1.0, step: 1.0,
value: 0.0, value: 0.0,
on_change: None,
bounds: Bounds::default(), bounds: Bounds::default(),
} }
} }
@ -63,12 +65,6 @@ impl Slider {
self 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. /// Set the value of the slider.
pub fn set_value(&mut self, value: f32, cx: &mut gpui::ViewContext<Self>) { pub fn set_value(&mut self, value: f32, cx: &mut gpui::ViewContext<Self>) {
self.value = value; self.value = value;
@ -114,9 +110,7 @@ impl Slider {
let value = (value / step).round() * step; let value = (value / step).round() * step;
self.value = value.clamp(self.min, self.max); self.value = value.clamp(self.min, self.max);
if let Some(on_change) = &self.on_change { cx.emit(SliderEvent::Change(self.value));
on_change(&self.value, cx);
}
cx.notify(); cx.notify();
} }
@ -160,6 +154,8 @@ impl Slider {
} }
} }
impl EventEmitter<SliderEvent> for Slider {}
impl Render for Slider { impl Render for Slider {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
div() div()