Imporve dropdown (#73)
* Remove min-height * Provide `render_empty` for custom empty view * Fix crash when delegate is empty
This commit is contained in:
parent
81ecb1e11b
commit
d278d85058
3 changed files with 67 additions and 31 deletions
|
|
@ -43,6 +43,7 @@ pub struct DropdownStory {
|
||||||
fruit_dropdown: View<Dropdown<Vec<SharedString>>>,
|
fruit_dropdown: View<Dropdown<Vec<SharedString>>>,
|
||||||
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
|
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
|
||||||
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
|
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
|
||||||
|
simple_dropdown3: View<Dropdown<Vec<SharedString>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DropdownStory {
|
impl DropdownStory {
|
||||||
|
|
@ -106,6 +107,18 @@ impl DropdownStory {
|
||||||
.placeholder("Language")
|
.placeholder("Language")
|
||||||
.title_prefix("Language: ")
|
.title_prefix("Language: ")
|
||||||
}),
|
}),
|
||||||
|
simple_dropdown3: cx.new_view(|cx| {
|
||||||
|
Dropdown::string_list("string-list3", Vec::<SharedString>::new(), None, cx)
|
||||||
|
.size(ui::Size::Small)
|
||||||
|
.render_empty(|cx| {
|
||||||
|
h_flex()
|
||||||
|
.h_24()
|
||||||
|
.justify_center()
|
||||||
|
.text_color(cx.theme().muted_foreground)
|
||||||
|
.child("No Data")
|
||||||
|
.into_any_element()
|
||||||
|
})
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -175,7 +188,8 @@ impl Render for DropdownStory {
|
||||||
.w_128()
|
.w_128()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.child(self.simple_dropdown1.clone())
|
.child(self.simple_dropdown1.clone())
|
||||||
.child(self.simple_dropdown2.clone()),
|
.child(self.simple_dropdown2.clone())
|
||||||
|
.child(self.simple_dropdown3.clone()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, rc::Rc};
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext,
|
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent,
|
||||||
ClickEvent, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
|
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView,
|
||||||
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement as _, Render,
|
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement, Render, SharedString,
|
||||||
SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
|
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, WeakView, WindowContext,
|
||||||
VisualContext as _, WeakView, WindowContext,
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
button::{Button, ButtonStyle},
|
||||||
|
h_flex,
|
||||||
|
list::{self, List, ListDelegate, ListItem},
|
||||||
|
styled_ext::StyleSized,
|
||||||
|
theme::ActiveTheme,
|
||||||
|
Clickable, Icon, IconName, Size, StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(dropdown, [Up, Down, Enter, Escape]);
|
actions!(dropdown, [Up, Down, Enter, Escape]);
|
||||||
|
|
@ -20,15 +28,6 @@ pub fn init(cx: &mut AppContext) {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::{
|
|
||||||
button::{Button, ButtonStyle},
|
|
||||||
h_flex,
|
|
||||||
list::{self, List, ListDelegate, ListItem},
|
|
||||||
styled_ext::StyleSized,
|
|
||||||
theme::ActiveTheme as _,
|
|
||||||
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.
|
||||||
pub trait DropdownItem {
|
pub trait DropdownItem {
|
||||||
type Value: Clone;
|
type Value: Clone;
|
||||||
|
|
@ -185,6 +184,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
title_prefix: Option<SharedString>,
|
title_prefix: Option<SharedString>,
|
||||||
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
selected_value: Option<<D::Item as DropdownItem>::Value>,
|
||||||
|
render_empty: Option<Rc<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Dropdown<D>
|
impl<D> Dropdown<D>
|
||||||
|
|
@ -214,6 +214,7 @@ where
|
||||||
open: false,
|
open: false,
|
||||||
cleanable: false,
|
cleanable: false,
|
||||||
title_prefix: None,
|
title_prefix: None,
|
||||||
|
render_empty: None,
|
||||||
};
|
};
|
||||||
this.set_selected_index(selected_index, cx);
|
this.set_selected_index(selected_index, cx);
|
||||||
this
|
this
|
||||||
|
|
@ -246,6 +247,14 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn render_empty<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(&WindowContext) -> AnyElement + 'static,
|
||||||
|
{
|
||||||
|
self.render_empty = Some(Rc::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_selected_index(
|
pub fn set_selected_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
|
|
@ -326,19 +335,24 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
|
let is_empty = self.list.read(cx).delegate().delegate.is_empty();
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.absolute()
|
|
||||||
.mt_1p5()
|
|
||||||
.bg(cx.theme().background)
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().input)
|
|
||||||
.rounded(px(cx.theme().radius))
|
|
||||||
.shadow_md()
|
|
||||||
.track_focus(&self.list.focus_handle(cx))
|
.track_focus(&self.list.focus_handle(cx))
|
||||||
.child(self.list.clone())
|
|
||||||
.on_mouse_down_out(|_, cx| {
|
.on_mouse_down_out(|_, cx| {
|
||||||
cx.dispatch_action(Box::new(Escape));
|
cx.dispatch_action(Box::new(Escape));
|
||||||
})
|
})
|
||||||
|
.map(|this| {
|
||||||
|
if is_empty {
|
||||||
|
if let Some(render_empty) = &self.render_empty {
|
||||||
|
with_style(this, cx).child(render_empty(cx))
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
with_style(this, cx).child(self.list.clone())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
fn display_title(&self, cx: &WindowContext) -> impl IntoElement {
|
||||||
|
|
@ -350,7 +364,7 @@ where
|
||||||
.delegate
|
.delegate
|
||||||
.get(*selected_index)
|
.get(*selected_index)
|
||||||
.map(|item| item.title().to_string())
|
.map(|item| item.title().to_string())
|
||||||
.unwrap();
|
.unwrap_or_default();
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.children(self.title_prefix.clone().map(|prefix| {
|
.children(self.title_prefix.clone().map(|prefix| {
|
||||||
|
|
@ -367,6 +381,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_style(d: Focusable<Div>, cx: &WindowContext) -> Focusable<Div> {
|
||||||
|
d.absolute()
|
||||||
|
.mt_1p5()
|
||||||
|
.bg(cx.theme().background)
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().input)
|
||||||
|
.rounded(px(cx.theme().radius))
|
||||||
|
.shadow_md()
|
||||||
|
}
|
||||||
|
|
||||||
impl Dropdown<Vec<SharedString>> {
|
impl Dropdown<Vec<SharedString>> {
|
||||||
pub fn string_list(
|
pub fn string_list(
|
||||||
id: impl Into<ElementId>,
|
id: impl Into<ElementId>,
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,14 @@ use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use crate::input::{InputEvent, 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};
|
||||||
use crate::IconName;
|
use crate::IconName;
|
||||||
use crate::{scroll::Scrollbar, v_flex};
|
use crate::{scroll::Scrollbar, v_flex};
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, FocusHandle,
|
actions, div, prelude::FluentBuilder, uniform_list, AppContext, FocusHandle, FocusableView,
|
||||||
FocusableView, InteractiveElement as _, IntoElement, KeyBinding, Length, ListSizingBehavior,
|
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton,
|
||||||
MouseButton, ParentElement as _, Render, Styled as _, Task, UniformListScrollHandle, View,
|
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
|
||||||
ViewContext, VisualContext as _,
|
|
||||||
};
|
};
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
|
|
||||||
|
|
@ -326,7 +325,6 @@ where
|
||||||
v_flex()
|
v_flex()
|
||||||
.flex_grow()
|
.flex_grow()
|
||||||
.relative()
|
.relative()
|
||||||
.min_h(px(100.))
|
|
||||||
.when_some(self.max_height, |this, h| this.max_h(h))
|
.when_some(self.max_height, |this, h| this.max_h(h))
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.when(items_count == 0, |this| {
|
.when(items_count == 0, |this| {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue