Imporve dropdown (#73)

* Remove min-height
* Provide `render_empty` for custom empty view
* Fix crash when delegate is empty
This commit is contained in:
Floyd Wang 2024-07-26 14:31:47 +08:00 committed by GitHub
parent 81ecb1e11b
commit d278d85058
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 67 additions and 31 deletions

View file

@ -43,6 +43,7 @@ pub struct DropdownStory {
fruit_dropdown: View<Dropdown<Vec<SharedString>>>,
simple_dropdown1: View<Dropdown<Vec<SharedString>>>,
simple_dropdown2: View<Dropdown<Vec<SharedString>>>,
simple_dropdown3: View<Dropdown<Vec<SharedString>>>,
}
impl DropdownStory {
@ -106,6 +107,18 @@ impl DropdownStory {
.placeholder("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()
.gap_2()
.child(self.simple_dropdown1.clone())
.child(self.simple_dropdown2.clone()),
.child(self.simple_dropdown2.clone())
.child(self.simple_dropdown3.clone()),
)
}
}

View file

@ -1,11 +1,19 @@
use std::borrow::Cow;
use std::{borrow::Cow, rc::Rc};
use gpui::{
actions, deferred, div, prelude::FluentBuilder as _, px, rems, AnyElement, AppContext,
ClickEvent, DismissEvent, Element, ElementId, EventEmitter, FocusHandle, FocusableView,
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement as _, Render,
SharedString, StatefulInteractiveElement as _, Styled as _, View, ViewContext,
VisualContext as _, WeakView, WindowContext,
actions, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, AppContext, ClickEvent,
DismissEvent, Div, Element, ElementId, EventEmitter, FocusHandle, Focusable, FocusableView,
InteractiveElement, IntoElement, KeyBinding, LayoutId, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, View, ViewContext, VisualContext, 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]);
@ -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.
pub trait DropdownItem {
type Value: Clone;
@ -185,6 +184,7 @@ pub struct Dropdown<D: DropdownDelegate + 'static> {
placeholder: SharedString,
title_prefix: Option<SharedString>,
selected_value: Option<<D::Item as DropdownItem>::Value>,
render_empty: Option<Rc<dyn Fn(&WindowContext) -> AnyElement + 'static>>,
}
impl<D> Dropdown<D>
@ -214,6 +214,7 @@ where
open: false,
cleanable: false,
title_prefix: None,
render_empty: None,
};
this.set_selected_index(selected_index, cx);
this
@ -246,6 +247,14 @@ where
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(
&mut self,
selected_index: Option<usize>,
@ -326,19 +335,24 @@ where
}
fn render_menu_content(&self, cx: &WindowContext) -> impl IntoElement {
let is_empty = self.list.read(cx).delegate().delegate.is_empty();
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))
.child(self.list.clone())
.on_mouse_down_out(|_, cx| {
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 {
@ -350,7 +364,7 @@ where
.delegate
.get(*selected_index)
.map(|item| item.title().to_string())
.unwrap();
.unwrap_or_default();
h_flex()
.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>> {
pub fn string_list(
id: impl Into<ElementId>,

View file

@ -3,15 +3,14 @@ use std::{cell::Cell, rc::Rc};
use crate::input::{InputEvent, TextInput};
use crate::scroll::ScrollbarState;
use crate::theme::{ActiveTheme, Colorize as _};
use crate::theme::{ActiveTheme, Colorize};
use crate::IconName;
use crate::{scroll::Scrollbar, v_flex};
use gpui::SharedString;
use gpui::{
actions, div, prelude::FluentBuilder as _, px, uniform_list, AppContext, FocusHandle,
FocusableView, InteractiveElement as _, IntoElement, KeyBinding, Length, ListSizingBehavior,
MouseButton, ParentElement as _, Render, Styled as _, Task, UniformListScrollHandle, View,
ViewContext, VisualContext as _,
actions, div, prelude::FluentBuilder, uniform_list, AppContext, FocusHandle, FocusableView,
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton,
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
};
use smol::Timer;
@ -326,7 +325,6 @@ where
v_flex()
.flex_grow()
.relative()
.min_h(px(100.))
.when_some(self.max_height, |this, h| this.max_h(h))
.overflow_hidden()
.when(items_count == 0, |this| {