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
|
- [ ] ContextMenu to let user copy, cut, paste
|
||||||
- [ ] Selection by mouse, drag to select text
|
- [ ] Selection by mouse, drag to select text
|
||||||
- [x] Cursor blinking
|
- [x] Cursor blinking
|
||||||
|
- [ ] Textarea
|
||||||
|
- [ ] Input icon
|
||||||
- [x] Button
|
- [x] Button
|
||||||
- [x] Button with Icon
|
- [x] Button with Icon
|
||||||
- [ ] IconButton
|
- [x] IconButton
|
||||||
- [ ] ToggleButton
|
|
||||||
- [x] Label
|
- [x] Label
|
||||||
- [x] Icon
|
- [x] Icon
|
||||||
- [x] Checkbox
|
- [x] Checkbox
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ use ui::{
|
||||||
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
|
h_flex, v_flex, Clickable, Disableable as _, Icon, IconName, Selectable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::section;
|
||||||
|
|
||||||
pub struct ButtonStory {}
|
pub struct ButtonStory {}
|
||||||
|
|
||||||
impl ButtonStory {
|
impl ButtonStory {
|
||||||
|
|
@ -22,23 +24,6 @@ impl ButtonStory {
|
||||||
|
|
||||||
impl Render for ButtonStory {
|
impl Render for ButtonStory {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
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()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,18 @@ use gpui::{
|
||||||
VisualContext, WindowContext,
|
VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ui::{input::TextInput, v_flex};
|
use ui::{input::TextInput, v_flex, Icon, IconName};
|
||||||
|
|
||||||
|
use crate::section;
|
||||||
|
|
||||||
pub struct InputStory {
|
pub struct InputStory {
|
||||||
input1: View<TextInput>,
|
input1: View<TextInput>,
|
||||||
input2: View<TextInput>,
|
input2: View<TextInput>,
|
||||||
mash_input: View<TextInput>,
|
mash_input: View<TextInput>,
|
||||||
disabled_input: View<TextInput>,
|
disabled_input: View<TextInput>,
|
||||||
|
prefix_input1: View<TextInput>,
|
||||||
|
suffix_input1: View<TextInput>,
|
||||||
|
both_input1: View<TextInput>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputStory {
|
impl InputStory {
|
||||||
|
|
@ -31,6 +36,23 @@ impl InputStory {
|
||||||
input
|
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 {
|
Self {
|
||||||
input1,
|
input1,
|
||||||
input2: cx.new_view(|cx| {
|
input2: cx.new_view(|cx| {
|
||||||
|
|
@ -45,6 +67,9 @@ impl InputStory {
|
||||||
input.set_disabled(true, cx);
|
input.set_disabled(true, cx);
|
||||||
input
|
input
|
||||||
}),
|
}),
|
||||||
|
prefix_input1,
|
||||||
|
suffix_input1,
|
||||||
|
both_input1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,14 +80,26 @@ impl InputStory {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for 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()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(self.input1.clone())
|
.child(
|
||||||
.child(self.input2.clone())
|
section("Normal Input", cx)
|
||||||
.child(self.disabled_input.clone())
|
.child(self.input1.clone())
|
||||||
.child(self.mash_input.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;
|
pub use tooltip_story::TooltipStory;
|
||||||
|
|
||||||
use gpui::{
|
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,
|
FocusableView, IntoElement, ParentElement, Render, SharedString, Styled as _, Task, View,
|
||||||
ViewContext, VisualContext, WindowContext,
|
ViewContext, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
@ -30,7 +30,7 @@ use workspace::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
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(
|
pub fn story_case(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
|
@ -40,6 +40,23 @@ pub fn story_case(
|
||||||
StoryContainer::new(name, description, cx)
|
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 {
|
pub struct StoryContainer {
|
||||||
focus_handle: gpui::FocusHandle,
|
focus_handle: gpui::FocusHandle,
|
||||||
name: SharedString,
|
name: SharedString,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crate::{button::ButtonSize, theme::ActiveTheme};
|
use crate::{button::ButtonSize, theme::ActiveTheme};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, svg, AnyElement, Hsla, IntoElement, RenderOnce, SharedString,
|
prelude::FluentBuilder as _, svg, AnyElement, AnyView, Hsla, IntoElement, Render, RenderOnce,
|
||||||
StyleRefinement, Styled, Svg, WindowContext,
|
SharedString, StyleRefinement, Styled, Svg, View, VisualContext, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
|
|
@ -44,6 +46,11 @@ impl IconName {
|
||||||
}
|
}
|
||||||
.into()
|
.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 {
|
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 {
|
impl RenderOnce for IconName {
|
||||||
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
fn render(self, _cx: &mut WindowContext) -> impl IntoElement {
|
||||||
Icon::new(self)
|
Icon::new(self)
|
||||||
|
|
@ -89,6 +102,11 @@ impl Icon {
|
||||||
|
|
||||||
self
|
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 {
|
impl Styled for Icon {
|
||||||
|
|
@ -122,3 +140,20 @@ impl From<Icon> for AnyElement {
|
||||||
val.into_any_element()
|
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 {
|
pub struct TextInput {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
|
prefix: Option<AnyView>,
|
||||||
|
suffix: Option<AnyView>,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
blink_cursor: Model<BlinkCursor>,
|
blink_cursor: Model<BlinkCursor>,
|
||||||
selected_range: Range<usize>,
|
selected_range: Range<usize>,
|
||||||
|
|
@ -102,6 +104,8 @@ impl TextInput {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
appearance: true,
|
appearance: true,
|
||||||
|
prefix: None,
|
||||||
|
suffix: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Observe the blink cursor to repaint the view when it changes.
|
// Observe the blink cursor to repaint the view when it changes.
|
||||||
|
|
@ -158,6 +162,24 @@ impl TextInput {
|
||||||
self
|
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.
|
/// Return the text of the input field.
|
||||||
pub fn text(&self) -> SharedString {
|
pub fn text(&self) -> SharedString {
|
||||||
self.text.clone()
|
self.text.clone()
|
||||||
|
|
@ -609,6 +631,7 @@ impl Element for TextElement {
|
||||||
impl Render for TextInput {
|
impl Render for TextInput {
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let focused = self.focus_handle.is_focused(cx);
|
let focused = self.focus_handle.is_focused(cx);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.key_context("TextInput")
|
.key_context("TextInput")
|
||||||
|
|
@ -654,8 +677,12 @@ impl Render for TextInput {
|
||||||
cx.theme().background
|
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(),
|
input: cx.view().clone(),
|
||||||
}))
|
}))
|
||||||
|
.when_some(self.suffix.clone(), |this, suffix| this.child(suffix))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue