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:
parent
cbbd20867f
commit
f84e8b82ef
6 changed files with 132 additions and 30 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue