Add prefix and suffix to Input. (#12)

<img width="393" alt="image"
src="https://github.com/huacnlee/gpui-component/assets/5518/5df8d269-ea04-4d42-95c5-a3358cdee9b5">
This commit is contained in:
Jason Lee 2024-07-01 23:30:06 +08:00 committed by GitHub
parent cbbd20867f
commit f84e8b82ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 132 additions and 30 deletions

View file

@ -24,10 +24,11 @@ This is an example of build app by using GPUI.
- [ ] ContextMenu to let user copy, cut, paste
- [ ] Selection by mouse, drag to select text
- [x] Cursor blinking
- [ ] Textarea
- [ ] Input icon
- [x] Button
- [x] Button with Icon
- [ ] IconButton
- [ ] ToggleButton
- [x] IconButton
- [x] Label
- [x] Icon
- [x] Checkbox

View file

@ -8,6 +8,8 @@ use ui::{
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
};
use crate::section;
pub struct ButtonStory {}
impl ButtonStory {
@ -22,23 +24,6 @@ impl ButtonStory {
impl Render for ButtonStory {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
fn section(title: impl Into<SharedString>, cx: &ViewContext<ButtonStory>) -> Div {
use ui::theme::ActiveTheme;
let theme = cx.theme();
h_flex()
.items_center()
.gap_4()
.p_4()
.w_full()
.rounded_lg()
.border_1()
.border_color(theme.border)
.flex_wrap()
.justify_around()
.child(div().flex_none().w_full().child(title.into()))
}
v_flex()
.w_full()
.justify_start()

View file

@ -3,13 +3,18 @@ use gpui::{
VisualContext, WindowContext,
};
use ui::{input::TextInput, v_flex};
use ui::{input::TextInput, v_flex, Icon, IconName};
use crate::section;
pub struct InputStory {
input1: View<TextInput>,
input2: View<TextInput>,
mash_input: View<TextInput>,
disabled_input: View<TextInput>,
prefix_input1: View<TextInput>,
suffix_input1: View<TextInput>,
both_input1: View<TextInput>,
}
impl InputStory {
@ -31,6 +36,23 @@ impl InputStory {
input
});
let prefix_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.prefix(IconName::Search.view(cx))
.placeholder("Search some thing...")
});
let suffix_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.suffix(IconName::Info.view(cx))
.placeholder("Info here...")
});
let both_input1 = cx.new_view(|cx| {
TextInput::new(cx)
.prefix(IconName::Search.view(cx))
.suffix(IconName::Info.view(cx))
.placeholder("This input have prefix and suffix.")
});
Self {
input1,
input2: cx.new_view(|cx| {
@ -45,6 +67,9 @@ impl InputStory {
input.set_disabled(true, cx);
input
}),
prefix_input1,
suffix_input1,
both_input1,
}
}
@ -55,14 +80,26 @@ impl InputStory {
}
impl Render for InputStory {
fn render(&mut self, _cx: &mut ViewContext<Self>) -> impl IntoElement {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
v_flex()
.size_full()
.justify_start()
.gap_3()
.child(self.input1.clone())
.child(self.input2.clone())
.child(self.disabled_input.clone())
.child(self.mash_input.clone())
.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("Preifx and Suffix", cx)
.child(self.prefix_input1.clone())
.child(self.both_input1.clone())
.child(self.suffix_input1.clone()),
)
}
}

View file

@ -19,7 +19,7 @@ pub use switch_story::SwitchStory;
pub use tooltip_story::TooltipStory;
use gpui::{
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, EventEmitter,
div, prelude::FluentBuilder as _, px, AnyElement, AnyView, AppContext, Div, EventEmitter,
FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View,
ViewContext, VisualContext, WindowContext,
};
@ -30,7 +30,7 @@ use workspace::{
};
use anyhow::Result;
use ui::{divider::Divider, label::Label, v_flex};
use ui::{divider::Divider, h_flex, label::Label, v_flex};
pub fn story_case(
name: &'static str,
@ -40,6 +40,23 @@ pub fn story_case(
StoryContainer::new(name, description, cx)
}
pub fn section(title: impl Into<SharedString>, cx: &WindowContext) -> Div {
use ui::theme::ActiveTheme;
let theme = cx.theme();
h_flex()
.items_center()
.gap_4()
.p_4()
.w_full()
.rounded_lg()
.border_1()
.border_color(theme.border)
.flex_wrap()
.justify_around()
.child(div().flex_none().w_full().child(title.into()))
}
pub struct StoryContainer {
focus_handle: gpui::FocusHandle,
name: SharedString,

View file

@ -1,7 +1,9 @@
use std::ops::Deref;
use crate::{button::ButtonSize, theme::ActiveTheme};
use gpui::{
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, RenderOnce, SharedString,
StyleRefinement, Styled, Svg, WindowContext,
prelude::FluentBuilder as _, svg, AnyElement, AnyView, Hsla, IntoElement, Render, RenderOnce,
SharedString, StyleRefinement, Styled, Svg, View, VisualContext, WindowContext,
};
#[derive(IntoElement)]
@ -44,6 +46,11 @@ impl IconName {
}
.into()
}
/// Return the icon as a View<Icon>
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
Icon::new(self).view(cx)
}
}
impl From<IconName> for Icon {
@ -52,6 +59,12 @@ impl From<IconName> for Icon {
}
}
impl From<IconName> for AnyElement {
fn from(val: IconName) -> Self {
Icon::new(val).into_any_element()
}
}
impl RenderOnce for IconName {
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
Icon::new(self)
@ -89,6 +102,11 @@ impl Icon {
self
}
/// Create a new view for the icon
pub fn view(self, cx: &mut WindowContext) -> View<Icon> {
cx.new_view(|_| self)
}
}
impl Styled for Icon {
@ -122,3 +140,20 @@ impl From<Icon> for AnyElement {
val.into_any_element()
}
}
impl Render for Icon {
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
let text_color = self.text_color.unwrap_or_else(|| cx.theme().foreground);
svg()
.flex_none()
.size_4()
.text_color(text_color)
.map(|this| match self.size {
ButtonSize::XSmall => this.size_3(),
ButtonSize::Small => this.size_3p5(),
ButtonSize::Medium => this.size_4(),
})
.path(self.path.clone())
}
}

View file

@ -73,6 +73,8 @@ pub fn init(cx: &mut AppContext) {
pub struct TextInput {
focus_handle: FocusHandle,
text: SharedString,
prefix: Option<AnyView>,
suffix: Option<AnyView>,
placeholder: SharedString,
blink_cursor: Model<BlinkCursor>,
selected_range: Range<usize>,
@ -102,6 +104,8 @@ impl TextInput {
disabled: false,
masked: false,
appearance: true,
prefix: None,
suffix: None,
};
// Observe the blink cursor to repaint the view when it changes.
@ -158,6 +162,24 @@ impl TextInput {
self
}
/// Set the prefix element of the input field, for example a search Icon.
pub fn prefix(mut self, prefix: impl Into<AnyView>) -> Self {
self.prefix = Some(prefix.into());
self
}
/// Set the placeholder text of the input field.
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.placeholder = placeholder.into();
self
}
/// Set the suffix element of the input field, for example a clear button.
pub fn suffix(mut self, suffix: impl Into<AnyView>) -> Self {
self.suffix = Some(suffix.into());
self
}
/// Return the text of the input field.
pub fn text(&self) -> SharedString {
self.text.clone()
@ -609,6 +631,7 @@ impl Element for TextElement {
impl Render for TextInput {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focused = self.focus_handle.is_focused(cx);
div()
.flex()
.key_context("TextInput")
@ -654,8 +677,12 @@ impl Render for TextInput {
cx.theme().background
})
})
.child(div().w_full().child(TextElement {
.when_some(self.prefix.clone(), |this, prefix| this.child(prefix))
.gap_1()
.items_center()
.child(div().flex_grow().overflow_x_hidden().child(TextElement {
input: cx.view().clone(),
}))
.when_some(self.suffix.clone(), |this, suffix| this.child(suffix))
}
}