Add cleanable to Input (#36)
This commit is contained in:
parent
ce7c1a3a34
commit
07e1296916
8 changed files with 220 additions and 59 deletions
|
|
@ -113,6 +113,8 @@ impl DropdownStory {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.size(ui::Size::Small)
|
.size(ui::Size::Small)
|
||||||
|
.placeholder("UI")
|
||||||
|
.title_prefix("UI: ")
|
||||||
}),
|
}),
|
||||||
simple_dropdown2: cx.new_view(|cx| {
|
simple_dropdown2: cx.new_view(|cx| {
|
||||||
Dropdown::string_list(
|
Dropdown::string_list(
|
||||||
|
|
@ -127,6 +129,8 @@ impl DropdownStory {
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
.size(ui::Size::Small)
|
.size(ui::Size::Small)
|
||||||
|
.placeholder("Language")
|
||||||
|
.title_prefix("Language: ")
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +156,7 @@ impl Render for DropdownStory {
|
||||||
.child(self.furit_dropdown.clone()),
|
.child(self.furit_dropdown.clone()),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
v_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.p_10()
|
.p_10()
|
||||||
|
|
@ -161,6 +165,22 @@ impl Render for DropdownStory {
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(cx.theme().border)
|
.border_color(cx.theme().border)
|
||||||
.gap_4()
|
.gap_4()
|
||||||
|
.child(format!(
|
||||||
|
"Country: {:?}",
|
||||||
|
self.country_dropdown.read(cx).selected_value()
|
||||||
|
))
|
||||||
|
.child(format!(
|
||||||
|
"Furit: {:?}",
|
||||||
|
self.furit_dropdown.read(cx).selected_value()
|
||||||
|
))
|
||||||
|
.child(format!(
|
||||||
|
"UI: {:?}",
|
||||||
|
self.simple_dropdown1.read(cx).selected_value()
|
||||||
|
))
|
||||||
|
.child(format!(
|
||||||
|
"Language: {:?}",
|
||||||
|
self.simple_dropdown2.read(cx).selected_value()
|
||||||
|
))
|
||||||
.child("This is other text."),
|
.child("This is other text."),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
|
|
|
||||||
|
|
@ -37,13 +37,13 @@ impl InputStory {
|
||||||
|
|
||||||
fn new(cx: &mut WindowContext) -> Self {
|
fn new(cx: &mut WindowContext) -> Self {
|
||||||
let input1 = cx.new_view(|cx| {
|
let input1 = cx.new_view(|cx| {
|
||||||
let mut input = TextInput::new(cx);
|
let mut input = TextInput::new(cx).cleanable(true);
|
||||||
input.set_text("Hello 世界", cx);
|
input.set_text("Hello 世界", cx);
|
||||||
input
|
input
|
||||||
});
|
});
|
||||||
|
|
||||||
let mask_input = cx.new_view(|cx| {
|
let mask_input = cx.new_view(|cx| {
|
||||||
let mut input = TextInput::new(cx);
|
let mut input = TextInput::new(cx).cleanable(true);
|
||||||
input.set_masked(true, cx);
|
input.set_masked(true, cx);
|
||||||
input.set_text("this-is-password", cx);
|
input.set_text("this-is-password", cx);
|
||||||
input
|
input
|
||||||
|
|
@ -53,16 +53,19 @@ impl InputStory {
|
||||||
TextInput::new(cx)
|
TextInput::new(cx)
|
||||||
.prefix(IconName::Search.view(cx))
|
.prefix(IconName::Search.view(cx))
|
||||||
.placeholder("Search some thing...")
|
.placeholder("Search some thing...")
|
||||||
|
.cleanable(true)
|
||||||
});
|
});
|
||||||
let suffix_input1 = cx.new_view(|cx| {
|
let suffix_input1 = cx.new_view(|cx| {
|
||||||
TextInput::new(cx)
|
TextInput::new(cx)
|
||||||
.suffix(IconName::Info.view(cx))
|
.suffix(IconName::Info.view(cx))
|
||||||
.placeholder("Info here...")
|
.placeholder("Info here...")
|
||||||
|
.cleanable(true)
|
||||||
});
|
});
|
||||||
let both_input1 = cx.new_view(|cx| {
|
let both_input1 = cx.new_view(|cx| {
|
||||||
TextInput::new(cx)
|
TextInput::new(cx)
|
||||||
.prefix(IconName::Search.view(cx))
|
.prefix(IconName::Search.view(cx))
|
||||||
.suffix(IconName::Info.view(cx))
|
.suffix(IconName::Info.view(cx))
|
||||||
|
.cleanable(true)
|
||||||
.placeholder("This input have prefix and suffix.")
|
.placeholder("This input have prefix and suffix.")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,13 @@ impl ListDelegate for CompanyListDelegate {
|
||||||
cx.dispatch_action(Box::new(SelectedCompany));
|
cx.dispatch_action(Box::new(SelectedCompany));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {
|
||||||
|
if let Some(ix) = ix {
|
||||||
|
self.selected_index = ix;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn render_item(&self, ix: usize, _cx: &mut ViewContext<List<Self>>) -> Option<Self::Item> {
|
fn render_item(&self, ix: usize, _cx: &mut ViewContext<List<Self>>) -> Option<Self::Item> {
|
||||||
let selected = ix == self.selected_index;
|
let selected = ix == self.selected_index;
|
||||||
if let Some(company) = self.companies.get(ix) {
|
if let Some(company) = self.companies.get(ix) {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,13 @@ impl ListDelegate for ListItemDeletegate {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {
|
||||||
|
if let Some(ix) = ix {
|
||||||
|
self.selected_index = ix;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PickerStory {
|
pub struct PickerStory {
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext,
|
actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext,
|
||||||
DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView, InteractiveElement,
|
ClickEvent, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
|
||||||
IntoElement, KeyBinding, LayoutId, ParentElement as _, Render, SharedString,
|
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement as _, Render,
|
||||||
StatefulInteractiveElement as _, Styled as _, View, ViewContext, VisualContext as _, WeakView,
|
SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
|
||||||
WindowContext,
|
VisualContext as _, WeakView, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(dropdown, [Up, Down, Enter, Escape]);
|
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||||
|
|
@ -21,11 +21,12 @@ pub fn init(cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
button::{Button, ButtonStyle},
|
||||||
h_flex,
|
h_flex,
|
||||||
list::{self, List, ListDelegate, ListItem},
|
list::{self, List, ListDelegate, ListItem},
|
||||||
styled_ext::Sizeful,
|
styled_ext::Sizeful,
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
Icon, IconName, Size, StyledExt,
|
Clickable as _, Icon, IconName, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A trait for items that can be displayed in a dropdown.
|
/// A trait for items that can be displayed in a dropdown.
|
||||||
|
|
@ -116,19 +117,19 @@ where
|
||||||
self.selected_index = ix;
|
self.selected_index = ix;
|
||||||
|
|
||||||
if let Some(view) = self.dropdown.upgrade() {
|
if let Some(view) = self.dropdown.upgrade() {
|
||||||
cx.update_view(&view, |view, cx| {
|
cx.update_view(&view, |view, _| {
|
||||||
if let Some(ix) = self.selected_index {
|
view.selected_value = self
|
||||||
if let Some(item) = self.delegate.get(ix) {
|
.selected_index
|
||||||
view.title = Some(item.title().to_string().into());
|
.and_then(|ix| self.delegate.get(ix))
|
||||||
view.value = Some(item.value().to_string().into());
|
.map(|item| item.value().to_string().into());
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.open = false;
|
view.open = false;
|
||||||
view.focus_handle.focus(cx);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_selected_index(&mut self, ix: Option<usize>, _: &mut ViewContext<List<Self>>) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StringDropdownDelegate {
|
pub struct StringDropdownDelegate {
|
||||||
|
|
@ -151,9 +152,10 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||||
list: View<List<DropdownListDelegate<D>>>,
|
list: View<List<DropdownListDelegate<D>>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
open: bool,
|
open: bool,
|
||||||
/// The value of the selected item.
|
cleanable: bool,
|
||||||
value: Option<SharedString>,
|
placeholder: SharedString,
|
||||||
title: Option<SharedString>,
|
title_prefix: Option<SharedString>,
|
||||||
|
selected_value: Option<SharedString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dropdown<D>
|
impl<D> Dropdown<D>
|
||||||
|
|
@ -172,30 +174,20 @@ where
|
||||||
selected_index,
|
selected_index,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (title, value) = if let Some(selected_index) = selected_index {
|
|
||||||
let title: Option<SharedString> = delegate
|
|
||||||
.delegate
|
|
||||||
.get(selected_index)
|
|
||||||
.map(|item| item.title().to_string().into());
|
|
||||||
let value: Option<SharedString> = delegate
|
|
||||||
.delegate
|
|
||||||
.get(selected_index)
|
|
||||||
.map(|item| item.value().to_string().into());
|
|
||||||
(title, value)
|
|
||||||
} else {
|
|
||||||
(None, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
let list = cx.new_view(|cx| List::new(delegate, cx).no_query().max_h(rems(20.)));
|
let list = cx.new_view(|cx| List::new(delegate, cx).no_query().max_h(rems(20.)));
|
||||||
Self {
|
let mut this = Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
placeholder: "Select...".into(),
|
||||||
list,
|
list,
|
||||||
size: Size::Medium,
|
size: Size::Medium,
|
||||||
|
selected_value: None,
|
||||||
open: false,
|
open: false,
|
||||||
title,
|
cleanable: true,
|
||||||
value,
|
title_prefix: None,
|
||||||
}
|
};
|
||||||
|
this.update_selected_value(cx);
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(mut self, size: Size) -> Self {
|
pub fn size(mut self, size: Size) -> Self {
|
||||||
|
|
@ -203,13 +195,52 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_value(&mut self, value: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
/// Set the placeholder for display when dropdown value is empty.
|
||||||
self.value = Some(value.into());
|
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
|
||||||
cx.notify();
|
self.placeholder = placeholder.into();
|
||||||
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value(&self) -> Option<SharedString> {
|
/// Set title prefix for the dropdown.
|
||||||
self.value.clone()
|
///
|
||||||
|
/// e.g.: Country: United States
|
||||||
|
///
|
||||||
|
/// You should set the label is `Country: `
|
||||||
|
pub fn title_prefix(mut self, prefix: impl Into<SharedString>) -> Self {
|
||||||
|
self.title_prefix = Some(prefix.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set true to show the clear button when the input field is not empty.
|
||||||
|
pub fn cleanable(mut self, cleanable: bool) -> Self {
|
||||||
|
self.cleanable = cleanable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_selected_index(
|
||||||
|
&mut self,
|
||||||
|
selected_index: Option<usize>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) {
|
||||||
|
self.list.update(cx, |list, cx| {
|
||||||
|
list.set_selected_index(selected_index, cx);
|
||||||
|
});
|
||||||
|
self.update_selected_value(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_index(&self, cx: &WindowContext) -> Option<usize> {
|
||||||
|
self.list.read(cx).selected_index()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_selected_value(&mut self, cx: &WindowContext) {
|
||||||
|
self.selected_value = self
|
||||||
|
.selected_index(cx)
|
||||||
|
.and_then(|ix| self.list.read(cx).delegate().delegate.get(ix))
|
||||||
|
.map(|item| item.value().to_string().into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_value(&self) -> Option<SharedString> {
|
||||||
|
self.selected_value.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||||
|
|
@ -239,11 +270,20 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn toggle_menu(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
self.open = !self.open;
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
|
||||||
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
fn escape(&mut self, _: &Escape, cx: &mut ViewContext<Self>) {
|
||||||
self.open = false;
|
self.open = false;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
self.set_selected_index(None, cx)
|
||||||
|
}
|
||||||
|
|
||||||
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
.absolute()
|
.absolute()
|
||||||
|
|
@ -259,6 +299,31 @@ where
|
||||||
cx.dispatch_action(Box::new(Escape));
|
cx.dispatch_action(Box::new(Escape));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
|
if let Some(selected_index) = &self.selected_index(cx) {
|
||||||
|
let title = self
|
||||||
|
.list
|
||||||
|
.read(cx)
|
||||||
|
.delegate()
|
||||||
|
.delegate
|
||||||
|
.get(*selected_index)
|
||||||
|
.map(|item| item.title().to_string())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
h_flex()
|
||||||
|
.children(self.title_prefix.clone().map(|prefix| {
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().accent_foreground)
|
||||||
|
.child(prefix.clone())
|
||||||
|
}))
|
||||||
|
.child(title.clone())
|
||||||
|
} else {
|
||||||
|
div()
|
||||||
|
.text_color(cx.theme().accent_foreground)
|
||||||
|
.child(self.placeholder.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dropdown<StringDropdownDelegate> {
|
impl Dropdown<StringDropdownDelegate> {
|
||||||
|
|
@ -291,10 +356,11 @@ where
|
||||||
D: DropdownDelegate + 'static,
|
D: DropdownDelegate + 'static,
|
||||||
{
|
{
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let title = self.title.clone().unwrap_or_else(|| "Select...".into());
|
|
||||||
let focused = self.focus_handle.is_focused(cx);
|
let focused = self.focus_handle.is_focused(cx);
|
||||||
|
let show_clean = self.cleanable && self.selected_index(cx).is_some();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
|
.id(self.id.clone())
|
||||||
.key_context("Dropdown")
|
.key_context("Dropdown")
|
||||||
.group(format!("dropdown-group:{}", self.id))
|
.group(format!("dropdown-group:{}", self.id))
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
|
|
@ -321,24 +387,33 @@ where
|
||||||
.input_px(self.size)
|
.input_px(self.size)
|
||||||
.input_py(self.size)
|
.input_py(self.size)
|
||||||
.input_h(self.size)
|
.input_h(self.size)
|
||||||
.on_click(cx.listener(|this, _, cx| {
|
.on_click(cx.listener(Self::toggle_menu))
|
||||||
this.open = !this.open;
|
|
||||||
cx.notify();
|
|
||||||
}))
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.child(div().flex_1().child(title))
|
.child(div().flex_1().child(self.display_title(cx)))
|
||||||
.child(
|
.when(show_clean, |this| {
|
||||||
Icon::new(IconName::ChevronDown)
|
this.child(
|
||||||
.text_color(cx.theme().muted_foreground),
|
Button::new("clean-text", cx)
|
||||||
),
|
.icon(IconName::Close)
|
||||||
|
.style(ButtonStyle::Ghost)
|
||||||
|
.size(px(14.))
|
||||||
|
.cursor_pointer()
|
||||||
|
.on_click(cx.listener(Self::clean)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.when(!show_clean, |this| {
|
||||||
|
this.child(
|
||||||
|
Icon::new(IconName::ChevronDown)
|
||||||
|
.text_color(cx.theme().muted_foreground),
|
||||||
|
)
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.child(DropdownMenuElement {
|
.child(DropdownMenuElement {
|
||||||
id: "dropdown-menu".into(),
|
id: ElementId::Name(format!("dropdown-menu:{}", self.id).into()),
|
||||||
dropdown: cx.view().clone(),
|
dropdown: cx.view().clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,15 @@
|
||||||
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
|
use crate::button::{Button, ButtonStyle};
|
||||||
use crate::styled_ext::Sizeful;
|
use crate::styled_ext::Sizeful;
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
use crate::StyledExt as _;
|
|
||||||
use crate::{event::InterativeElementExt as _, Size};
|
use crate::{event::InterativeElementExt as _, Size};
|
||||||
|
use crate::{Clickable, IconName, StyledExt as _};
|
||||||
use blink_cursor::BlinkCursor;
|
use blink_cursor::BlinkCursor;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, fill, point, prelude, px, relative, rems, size, AnyView, AppContext, Bounds,
|
actions, div, fill, point, prelude, px, relative, rems, size, AnyView, AppContext, Bounds,
|
||||||
ClipboardItem, Context as _, Element, ElementId, ElementInputHandler, EventEmitter,
|
ClickEvent, ClipboardItem, Context as _, Element, ElementId, ElementInputHandler, EventEmitter,
|
||||||
FocusHandle, FocusableView, GlobalElementId, InteractiveElement as _, IntoElement, KeyBinding,
|
FocusHandle, FocusableView, GlobalElementId, InteractiveElement as _, IntoElement, KeyBinding,
|
||||||
KeyDownEvent, LayoutId, Model, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
KeyDownEvent, LayoutId, Model, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
PaintQuad, ParentElement as _, Pixels, Point, Render, ShapedLine, SharedString, Style,
|
PaintQuad, ParentElement as _, Pixels, Point, Render, ShapedLine, SharedString, Style,
|
||||||
|
|
@ -103,6 +104,7 @@ pub struct TextInput {
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
masked: bool,
|
masked: bool,
|
||||||
appearance: bool,
|
appearance: bool,
|
||||||
|
cleanable: bool,
|
||||||
size: Size,
|
size: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,6 +128,7 @@ impl TextInput {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
appearance: true,
|
appearance: true,
|
||||||
|
cleanable: false,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
suffix: None,
|
suffix: None,
|
||||||
size: Size::Medium,
|
size: Size::Medium,
|
||||||
|
|
@ -202,12 +205,19 @@ impl TextInput {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set true to show the clear button when the input field is not empty.
|
||||||
|
pub fn cleanable(mut self, cleanable: bool) -> Self {
|
||||||
|
self.cleanable = cleanable;
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -216,6 +226,7 @@ impl TextInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -237,10 +248,12 @@ impl TextInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
self.move_to(0, cx);
|
self.move_to(0, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
self.move_to(self.text.len(), cx);
|
self.move_to(self.text.len(), cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,13 +269,18 @@ impl TextInput {
|
||||||
if self.selected_range.is_empty() {
|
if self.selected_range.is_empty() {
|
||||||
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
||||||
}
|
}
|
||||||
self.replace_text_in_range(None, "", cx)
|
self.replace_text_in_range(None, "", cx);
|
||||||
|
self.pause_blink_cursor(cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||||
cx.emit(TextEvent::PressEnter);
|
cx.emit(TextEvent::PressEnter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
self.set_text("", cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
|
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
|
||||||
self.is_selecting = true;
|
self.is_selecting = true;
|
||||||
|
|
||||||
|
|
@ -784,6 +802,16 @@ impl Render for TextInput {
|
||||||
input: cx.view().clone(),
|
input: cx.view().clone(),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.when(self.cleanable && !self.text.is_empty(), |this| {
|
||||||
|
this.child(
|
||||||
|
Button::new("clean-text", cx)
|
||||||
|
.icon(IconName::Close)
|
||||||
|
.style(ButtonStyle::Ghost)
|
||||||
|
.size(px(14.))
|
||||||
|
.cursor_pointer()
|
||||||
|
.on_click(cx.listener(Self::clean)),
|
||||||
|
)
|
||||||
|
})
|
||||||
.when_some(self.suffix.clone(), |this, suffix| this.child(suffix))
|
.when_some(self.suffix.clone(), |this, suffix| this.child(suffix))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,21 @@ pub fn init(cx: &mut AppContext) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A delegate for the List.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub trait ListDelegate: Sized + 'static {
|
pub trait ListDelegate: Sized + 'static {
|
||||||
type Item: IntoElement;
|
type Item: IntoElement;
|
||||||
|
|
||||||
|
/// When Query Input change, this method will be called.
|
||||||
|
/// You can perform search here.
|
||||||
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) {}
|
fn perform_search(&mut self, query: &str, cx: &mut ViewContext<List<Self>>) {}
|
||||||
|
|
||||||
/// Return the number of items in the list.
|
/// Return the number of items in the list.
|
||||||
fn items_count(&self) -> usize;
|
fn items_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Render the item at the given index.
|
||||||
|
///
|
||||||
|
/// Return None will skip the item.
|
||||||
fn render_item(&self, ix: usize, cx: &mut ViewContext<List<Self>>) -> Option<Self::Item>;
|
fn render_item(&self, ix: usize, cx: &mut ViewContext<List<Self>>) -> Option<Self::Item>;
|
||||||
|
|
||||||
/// Return the confirmed index of the selected item.
|
/// Return the confirmed index of the selected item.
|
||||||
|
|
@ -41,8 +48,13 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the confirm and give the selected index.
|
/// Set the selected index, just store the ix, don't confirm.
|
||||||
|
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>);
|
||||||
|
|
||||||
|
/// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter.
|
||||||
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {}
|
fn confirm(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {}
|
||||||
|
|
||||||
|
/// Cancel the selection, e.g.: Pressed ESC.
|
||||||
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {}
|
fn cancel(&mut self, cx: &mut ViewContext<List<Self>>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,6 +125,15 @@ where
|
||||||
cx.focus(&self.focus_handle);
|
cx.focus(&self.focus_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<Self>) {
|
||||||
|
self.selected_index = ix;
|
||||||
|
self.delegate.set_selected_index(ix, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_index(&self) -> Option<usize> {
|
||||||
|
self.selected_index
|
||||||
|
}
|
||||||
|
|
||||||
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
fn render_scrollbar(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
|
||||||
if !self.enable_scrollbar {
|
if !self.enable_scrollbar {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ impl<T: Styled> Sizeful<T> for T {
|
||||||
fn input_h(self, size: Size) -> Self {
|
fn input_h(self, size: Size) -> Self {
|
||||||
match size {
|
match size {
|
||||||
Size::Large => self.h_11().text_size(rems(1.)),
|
Size::Large => self.h_11().text_size(rems(1.)),
|
||||||
Size::Medium => self.h_8().text_size(rems(0.85)),
|
Size::Medium => self.h_8().text_size(rems(0.875)),
|
||||||
_ => self.h(px(26.)).text_size(rems(0.8)),
|
_ => self.h(px(26.)).text_size(rems(0.8)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue