list: Update ListItem and DropdownListItem style. (#1095)
## Break Change - The `Selectable` trait has been removed `element_id` method, this is necessary. - The `item` of `List` now must impl `Selectable` trait. ## List > NOTE: The default ListItem is no rounded and paddings. <img width="1144" height="762" alt="image" src="https://github.com/user-attachments/assets/32348682-f89a-487a-af9f-5ed379a7f2d8" /> ## Dropdown <img width="537" height="387" alt="image" src="https://github.com/user-attachments/assets/3cc99d9f-3714-4d10-b467-6c91917bcbf0" />
This commit is contained in:
parent
6d0443998c
commit
93b578294c
15 changed files with 304 additions and 174 deletions
|
|
@ -71,15 +71,11 @@ impl ListDelegate for ListItemDeletegate {
|
||||||
_: &mut Context<List<Self>>,
|
_: &mut Context<List<Self>>,
|
||||||
) -> Option<Self::Item> {
|
) -> Option<Self::Item> {
|
||||||
let confirmed = Some(ix) == self.confirmed_index;
|
let confirmed = Some(ix) == self.confirmed_index;
|
||||||
let selected = Some(ix) == self.selected_index;
|
|
||||||
|
|
||||||
if let Some(item) = self.matches.get(ix) {
|
if let Some(item) = self.matches.get(ix) {
|
||||||
let list_item = ListItem::new(("item", ix))
|
let list_item = ListItem::new(("item", ix))
|
||||||
.check_icon(IconName::Check)
|
.check_icon(IconName::Check)
|
||||||
.confirmed(confirmed)
|
.confirmed(confirmed)
|
||||||
.selected(selected)
|
|
||||||
.py_1()
|
|
||||||
.px_3()
|
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::time::Duration;
|
||||||
|
|
||||||
use fake::Fake;
|
use fake::Fake;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, px, App, AppContext, Context, ElementId, Entity, FocusHandle, Focusable,
|
actions, div, prelude::FluentBuilder as _, px, App, AppContext, Context, Edges, ElementId,
|
||||||
InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, SharedString, Styled,
|
Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, ParentElement, Render,
|
||||||
Subscription, Task, Timer, Window,
|
RenderOnce, SharedString, Styled, Subscription, Task, Timer, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gpui_component::{
|
use gpui_component::{
|
||||||
|
|
@ -13,7 +13,7 @@ use gpui_component::{
|
||||||
h_flex,
|
h_flex,
|
||||||
label::Label,
|
label::Label,
|
||||||
list::{List, ListDelegate, ListEvent, ListItem},
|
list::{List, ListDelegate, ListEvent, ListItem},
|
||||||
v_flex, ActiveTheme, Sizable,
|
v_flex, ActiveTheme, Selectable, Sizable,
|
||||||
};
|
};
|
||||||
|
|
||||||
actions!(list_story, [SelectedCompany]);
|
actions!(list_story, [SelectedCompany]);
|
||||||
|
|
@ -65,6 +65,17 @@ impl CompanyListItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Selectable for CompanyListItem {
|
||||||
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selected(&self) -> bool {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for CompanyListItem {
|
impl RenderOnce for CompanyListItem {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let text_color = if self.selected {
|
let text_color = if self.selected {
|
||||||
|
|
@ -88,10 +99,16 @@ impl RenderOnce for CompanyListItem {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.px_3()
|
.px_2()
|
||||||
.py_1()
|
.py_1()
|
||||||
.overflow_x_hidden()
|
.overflow_x_hidden()
|
||||||
.bg(bg_color)
|
.bg(bg_color)
|
||||||
|
.border_1()
|
||||||
|
.border_color(bg_color)
|
||||||
|
.when(self.selected, |this| {
|
||||||
|
this.border_color(cx.theme().list_active_border)
|
||||||
|
})
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|
@ -283,10 +300,9 @@ impl ListStory {
|
||||||
eof: false,
|
eof: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let company_list = cx.new(|cx| List::new(delegate, window, cx));
|
let company_list =
|
||||||
// company_list.update(cx, |list, cx| {
|
cx.new(|cx| List::new(delegate, window, cx).paddings(Edges::all(px(8.))));
|
||||||
// list.set_selected_index(Some(3), cx);
|
|
||||||
// });
|
|
||||||
let _subscriptions =
|
let _subscriptions =
|
||||||
vec![
|
vec![
|
||||||
cx.subscribe(&company_list, |_, _, ev: &ListEvent, _| match ev {
|
cx.subscribe(&company_list, |_, _, ev: &ListEvent, _| match ev {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, relative, Action, AnyElement, App, ClickEvent, Corners, Div,
|
div, prelude::FluentBuilder as _, relative, Action, AnyElement, App, ClickEvent, Corners, Div,
|
||||||
Edges, ElementId, Hsla, InteractiveElement, Interactivity, IntoElement, MouseButton,
|
Edges, ElementId, Hsla, InteractiveElement, Interactivity, IntoElement, MouseButton,
|
||||||
ParentElement, Pixels, RenderOnce, SharedString, StatefulInteractiveElement as _,
|
ParentElement, Pixels, RenderOnce, SharedString, Stateful, StatefulInteractiveElement as _,
|
||||||
StyleRefinement, Styled, Window,
|
StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -181,9 +181,8 @@ impl ButtonVariant {
|
||||||
/// A Button element.
|
/// A Button element.
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
base: Div,
|
base: Stateful<Div>,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
id: ElementId,
|
|
||||||
icon: Option<Icon>,
|
icon: Option<Icon>,
|
||||||
label: Option<SharedString>,
|
label: Option<SharedString>,
|
||||||
children: Vec<AnyElement>,
|
children: Vec<AnyElement>,
|
||||||
|
|
@ -215,9 +214,8 @@ impl From<Button> for AnyElement {
|
||||||
impl Button {
|
impl Button {
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
base: div().flex_shrink_0(),
|
base: div().id(id.into()).flex_shrink_0(),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
id: id.into(),
|
|
||||||
icon: None,
|
icon: None,
|
||||||
label: None,
|
label: None,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|
@ -335,10 +333,6 @@ impl Disableable for Button {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for Button {
|
impl Selectable for Button {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self
|
self
|
||||||
|
|
@ -391,7 +385,6 @@ impl RenderOnce for Button {
|
||||||
};
|
};
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
.id(self.id)
|
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.cursor_default()
|
.cursor_default()
|
||||||
.flex()
|
.flex()
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,6 @@ impl ButtonVariants for DropdownButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for DropdownButton {
|
impl Selectable for DropdownButton {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -76,10 +76,6 @@ impl Disableable for Checkbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for Checkbox {
|
impl Selectable for Checkbox {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(self, selected: bool) -> Self {
|
fn selected(self, selected: bool) -> Self {
|
||||||
self.checked(selected)
|
self.checked(selected)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, AppContext,
|
anchored, canvas, deferred, div, prelude::FluentBuilder, px, rems, AnyElement, App, AppContext,
|
||||||
Bounds, ClickEvent, Context, DismissEvent, ElementId, Empty, Entity, EventEmitter, FocusHandle,
|
Bounds, ClickEvent, Context, DismissEvent, Edges, ElementId, Empty, Entity, EventEmitter,
|
||||||
Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement, Pixels, Render,
|
FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ParentElement,
|
||||||
RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Subscription,
|
Pixels, Render, RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled,
|
||||||
Task, WeakEntity, Window,
|
Subscription, Task, WeakEntity, Window,
|
||||||
};
|
};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
|
|
||||||
|
|
@ -11,8 +11,9 @@ use crate::{
|
||||||
actions::{Cancel, Confirm, SelectNext, SelectPrev},
|
actions::{Cancel, Confirm, SelectNext, SelectPrev},
|
||||||
h_flex,
|
h_flex,
|
||||||
input::clear_button,
|
input::clear_button,
|
||||||
list::{List, ListDelegate, ListItem},
|
list::{List, ListDelegate},
|
||||||
v_flex, ActiveTheme, Disableable as _, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
|
v_flex, ActiveTheme, Disableable, Icon, IconName, Selectable, Sizable, Size, StyleSized,
|
||||||
|
StyledExt,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
@ -135,7 +136,7 @@ impl<D> ListDelegate for DropdownListDelegate<D>
|
||||||
where
|
where
|
||||||
D: DropdownDelegate + 'static,
|
D: DropdownDelegate + 'static,
|
||||||
{
|
{
|
||||||
type Item = ListItem;
|
type Item = DropdownListItem;
|
||||||
|
|
||||||
fn items_count(&self, _: &App) -> usize {
|
fn items_count(&self, _: &App) -> usize {
|
||||||
self.delegate.len()
|
self.delegate.len()
|
||||||
|
|
@ -156,11 +157,9 @@ where
|
||||||
.map_or(Size::Medium, |dropdown| dropdown.read(cx).size);
|
.map_or(Size::Medium, |dropdown| dropdown.read(cx).size);
|
||||||
|
|
||||||
if let Some(item) = self.delegate.get(ix) {
|
if let Some(item) = self.delegate.get(ix) {
|
||||||
let list_item = ListItem::new(("list-item", ix))
|
let list_item = DropdownListItem::new(ix)
|
||||||
.check_icon(IconName::Check)
|
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.input_text_size(size)
|
.with_size(size)
|
||||||
.list_size(size)
|
|
||||||
.child(div().whitespace_nowrap().child(item.title().to_string()));
|
.child(div().whitespace_nowrap().child(item.title().to_string()));
|
||||||
Some(list_item)
|
Some(list_item)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -354,6 +353,7 @@ where
|
||||||
let list = cx.new(|cx| {
|
let list = cx.new(|cx| {
|
||||||
let mut list = List::new(delegate, window, cx)
|
let mut list = List::new(delegate, window, cx)
|
||||||
.max_h(rems(20.))
|
.max_h(rems(20.))
|
||||||
|
.paddings(Edges::all(px(4.)))
|
||||||
.reset_on_cancel(false);
|
.reset_on_cancel(false);
|
||||||
if !searchable {
|
if !searchable {
|
||||||
list = list.no_query();
|
list = list.no_query();
|
||||||
|
|
@ -835,3 +835,100 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(IntoElement)]
|
||||||
|
struct DropdownListItem {
|
||||||
|
id: ElementId,
|
||||||
|
size: Size,
|
||||||
|
style: StyleRefinement,
|
||||||
|
selected: bool,
|
||||||
|
disabled: bool,
|
||||||
|
children: Vec<AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DropdownListItem {
|
||||||
|
pub fn new(ix: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
id: ("dropdown-item", ix).into(),
|
||||||
|
size: Size::default(),
|
||||||
|
style: StyleRefinement::default(),
|
||||||
|
selected: false,
|
||||||
|
disabled: false,
|
||||||
|
children: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ParentElement for DropdownListItem {
|
||||||
|
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
|
||||||
|
self.children.extend(elements);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Disableable for DropdownListItem {
|
||||||
|
fn disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.disabled = disabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Selectable for DropdownListItem {
|
||||||
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
|
self.selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_selected(&self) -> bool {
|
||||||
|
self.selected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sizable for DropdownListItem {
|
||||||
|
fn with_size(mut self, size: impl Into<Size>) -> Self {
|
||||||
|
self.size = size.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Styled for DropdownListItem {
|
||||||
|
fn style(&mut self) -> &mut StyleRefinement {
|
||||||
|
&mut self.style
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderOnce for DropdownListItem {
|
||||||
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
h_flex()
|
||||||
|
.id(self.id)
|
||||||
|
.relative()
|
||||||
|
.gap_x_1()
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.rounded(cx.theme().radius)
|
||||||
|
.text_base()
|
||||||
|
.text_color(cx.theme().foreground)
|
||||||
|
.relative()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.input_text_size(self.size)
|
||||||
|
.list_size(self.size)
|
||||||
|
.refine_style(&self.style)
|
||||||
|
.when(!self.disabled, |this| {
|
||||||
|
this.when(!self.selected, |this| {
|
||||||
|
this.hover(|this| this.bg(cx.theme().accent.alpha(0.7)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.when(self.selected, |this| this.bg(cx.theme().accent))
|
||||||
|
.when(self.disabled, |this| {
|
||||||
|
this.text_color(cx.theme().muted_foreground)
|
||||||
|
})
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.w_full()
|
||||||
|
.items_center()
|
||||||
|
.justify_between()
|
||||||
|
.gap_x_1()
|
||||||
|
.child(div().w_full().children(self.children)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
use crate::actions::{Cancel, Confirm, SelectNext, SelectPrev};
|
use crate::actions::{Cancel, Confirm, SelectNext, SelectPrev};
|
||||||
use crate::input::InputState;
|
use crate::input::InputState;
|
||||||
use crate::{h_flex, Icon, Sizable as _};
|
use crate::{h_flex, Icon, Selectable, Sizable as _, StyledExt};
|
||||||
use crate::{
|
use crate::{
|
||||||
input::{InputEvent, TextInput},
|
input::{InputEvent, TextInput},
|
||||||
scroll::{Scrollbar, ScrollbarState},
|
scroll::{Scrollbar, ScrollbarState},
|
||||||
|
|
@ -14,7 +14,9 @@ use gpui::{
|
||||||
Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior,
|
Focusable, InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior,
|
||||||
MouseButton, ParentElement, Render, Styled, Task, UniformListScrollHandle, Window,
|
MouseButton, ParentElement, Render, Styled, Task, UniformListScrollHandle, Window,
|
||||||
};
|
};
|
||||||
use gpui::{px, App, Context, EventEmitter, MouseDownEvent, ScrollStrategy, Subscription};
|
use gpui::{
|
||||||
|
App, Context, Edges, EventEmitter, MouseDownEvent, Pixels, ScrollStrategy, Subscription,
|
||||||
|
};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use smol::Timer;
|
use smol::Timer;
|
||||||
|
|
||||||
|
|
@ -44,7 +46,7 @@ pub enum ListEvent {
|
||||||
/// A delegate for the List.
|
/// A delegate for the List.
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
pub trait ListDelegate: Sized + 'static {
|
pub trait ListDelegate: Sized + 'static {
|
||||||
type Item: IntoElement;
|
type Item: Selectable + IntoElement;
|
||||||
|
|
||||||
/// When Query Input change, this method will be called.
|
/// When Query Input change, this method will be called.
|
||||||
/// You can perform search here.
|
/// You can perform search here.
|
||||||
|
|
@ -82,7 +84,8 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
|
|
||||||
/// Returns Some(AnyElement) to render the initial state of the list.
|
/// Returns Some(AnyElement) to render the initial state of the list.
|
||||||
///
|
///
|
||||||
/// This can be used to show a view for the list before the user has interacted with it.
|
/// This can be used to show a view for the list before the user has
|
||||||
|
/// interacted with it.
|
||||||
///
|
///
|
||||||
/// For example: The last search results, or the last selected item.
|
/// For example: The last search results, or the last selected item.
|
||||||
///
|
///
|
||||||
|
|
@ -100,7 +103,8 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a Element to show when loading, default is built-in Skeleton loading view.
|
/// Returns a Element to show when loading, default is built-in Skeleton
|
||||||
|
/// loading view.
|
||||||
fn render_loading(
|
fn render_loading(
|
||||||
&self,
|
&self,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
|
|
@ -117,7 +121,8 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
cx: &mut Context<List<Self>>,
|
cx: &mut Context<List<Self>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Set the confirm and give the selected index, this is means user have clicked the item or pressed Enter.
|
/// Set the confirm and give the selected index,
|
||||||
|
/// this is means user have clicked the item or pressed Enter.
|
||||||
///
|
///
|
||||||
/// This will always to `set_selected_index` before confirm.
|
/// This will always to `set_selected_index` before confirm.
|
||||||
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
fn confirm(&mut self, secondary: bool, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
||||||
|
|
@ -132,8 +137,10 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a threshold value (n rows), of course, when scrolling to the bottom,
|
/// Returns a threshold value (n rows), of course,
|
||||||
/// the remaining number of rows triggers `load_more`.
|
/// when scrolling to the bottom, the remaining number of rows
|
||||||
|
/// triggers `load_more`.
|
||||||
|
///
|
||||||
/// This should smaller than the total number of first load rows.
|
/// This should smaller than the total number of first load rows.
|
||||||
///
|
///
|
||||||
/// Default: 20 rows
|
/// Default: 20 rows
|
||||||
|
|
@ -146,7 +153,8 @@ pub trait ListDelegate: Sized + 'static {
|
||||||
/// This will performed in a background task.
|
/// This will performed in a background task.
|
||||||
///
|
///
|
||||||
/// This is always called when the table is near the bottom,
|
/// This is always called when the table is near the bottom,
|
||||||
/// so you must check if there is more data to load or lock the loading state.
|
/// so you must check if there is more data to load or lock
|
||||||
|
/// the loading state.
|
||||||
fn load_more(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
fn load_more(&mut self, window: &mut Window, cx: &mut Context<List<Self>>) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -154,6 +162,7 @@ pub struct List<D: ListDelegate> {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
delegate: D,
|
delegate: D,
|
||||||
max_height: Option<Length>,
|
max_height: Option<Length>,
|
||||||
|
paddings: Edges<Pixels>,
|
||||||
query_input: Option<Entity<InputState>>,
|
query_input: Option<Entity<InputState>>,
|
||||||
last_query: Option<String>,
|
last_query: Option<String>,
|
||||||
selectable: bool,
|
selectable: bool,
|
||||||
|
|
@ -163,7 +172,7 @@ pub struct List<D: ListDelegate> {
|
||||||
scroll_state: ScrollbarState,
|
scroll_state: ScrollbarState,
|
||||||
pub(crate) size: Size,
|
pub(crate) size: Size,
|
||||||
selected_index: Option<usize>,
|
selected_index: Option<usize>,
|
||||||
right_clicked_index: Option<usize>,
|
mouse_right_clicked_index: Option<usize>,
|
||||||
reset_on_cancel: bool,
|
reset_on_cancel: bool,
|
||||||
_search_task: Task<()>,
|
_search_task: Task<()>,
|
||||||
_load_more_task: Task<()>,
|
_load_more_task: Task<()>,
|
||||||
|
|
@ -175,11 +184,8 @@ where
|
||||||
D: ListDelegate,
|
D: ListDelegate,
|
||||||
{
|
{
|
||||||
pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
pub fn new(delegate: D, window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||||
let query_input = cx.new(|cx| {
|
let query_input =
|
||||||
InputState::new(window, cx)
|
cx.new(|cx| InputState::new(window, cx).placeholder(t!("List.search_placeholder")));
|
||||||
// .prefix(|_, cx| Icon::new(IconName::Search).text_color(cx.theme().muted_foreground))
|
|
||||||
.placeholder(t!("List.search_placeholder"))
|
|
||||||
});
|
|
||||||
|
|
||||||
let _query_input_subscription =
|
let _query_input_subscription =
|
||||||
cx.subscribe_in(&query_input, window, Self::on_query_input_event);
|
cx.subscribe_in(&query_input, window, Self::on_query_input_event);
|
||||||
|
|
@ -190,7 +196,7 @@ where
|
||||||
query_input: Some(query_input),
|
query_input: Some(query_input),
|
||||||
last_query: None,
|
last_query: None,
|
||||||
selected_index: None,
|
selected_index: None,
|
||||||
right_clicked_index: None,
|
mouse_right_clicked_index: None,
|
||||||
vertical_scroll_handle: UniformListScrollHandle::new(),
|
vertical_scroll_handle: UniformListScrollHandle::new(),
|
||||||
scroll_state: ScrollbarState::default(),
|
scroll_state: ScrollbarState::default(),
|
||||||
max_height: None,
|
max_height: None,
|
||||||
|
|
@ -199,6 +205,7 @@ where
|
||||||
querying: false,
|
querying: false,
|
||||||
size: Size::default(),
|
size: Size::default(),
|
||||||
reset_on_cancel: true,
|
reset_on_cancel: true,
|
||||||
|
paddings: Edges::default(),
|
||||||
_search_task: Task::ready(()),
|
_search_task: Task::ready(()),
|
||||||
_load_more_task: Task::ready(()),
|
_load_more_task: Task::ready(()),
|
||||||
_query_input_subscription,
|
_query_input_subscription,
|
||||||
|
|
@ -260,7 +267,8 @@ where
|
||||||
self.focus_handle(cx).focus(window);
|
self.focus_handle(cx).focus(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the selected index of the list, this will also scroll to the selected item.
|
/// Set the selected index of the list,
|
||||||
|
/// this will also scroll to the selected item.
|
||||||
fn _set_selected_index(
|
fn _set_selected_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
ix: Option<usize>,
|
ix: Option<usize>,
|
||||||
|
|
@ -272,7 +280,8 @@ where
|
||||||
self.scroll_to_selected_item(window, cx);
|
self.scroll_to_selected_item(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the selected index of the list, this method will not scroll to the selected item.
|
/// Set the selected index of the list,
|
||||||
|
/// this method will not scroll to the selected item.
|
||||||
pub fn set_selected_index(
|
pub fn set_selected_index(
|
||||||
&mut self,
|
&mut self,
|
||||||
ix: Option<usize>,
|
ix: Option<usize>,
|
||||||
|
|
@ -317,6 +326,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set paddings for the list.
|
||||||
|
pub fn paddings(mut self, paddings: Edges<Pixels>) -> Self {
|
||||||
|
self.paddings = paddings;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn on_query_input_event(
|
fn on_query_input_event(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &Entity<InputState>,
|
_: &Entity<InputState>,
|
||||||
|
|
@ -375,7 +390,8 @@ where
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch delegate's `load_more` method when the visible range is near the end.
|
/// Dispatch delegate's `load_more` method when the
|
||||||
|
/// visible range is near the end.
|
||||||
fn load_more_if_need(
|
fn load_more_if_need(
|
||||||
&mut self,
|
&mut self,
|
||||||
items_count: usize,
|
items_count: usize,
|
||||||
|
|
@ -384,7 +400,8 @@ where
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let threshold = self.delegate.load_more_threshold();
|
let threshold = self.delegate.load_more_threshold();
|
||||||
// Securely handle subtract logic to prevent attempt to subtract with overflow
|
// Securely handle subtract logic to prevent attempt
|
||||||
|
// to subtract with overflow
|
||||||
if visible_end >= items_count.saturating_sub(threshold) {
|
if visible_end >= items_count.saturating_sub(threshold) {
|
||||||
if !self.delegate.can_load_more(cx) {
|
if !self.delegate.can_load_more(cx) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -500,31 +517,21 @@ where
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let selected = self.selected_index == Some(ix);
|
let selected = self.selected_index == Some(ix);
|
||||||
let right_clicked = self.right_clicked_index == Some(ix);
|
let mouse_right_clicked = self.mouse_right_clicked_index == Some(ix);
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("list-item")
|
.id("list-item")
|
||||||
.w_full()
|
.w_full()
|
||||||
.relative()
|
.relative()
|
||||||
.children(self.delegate.render_item(ix, window, cx))
|
.children(self.delegate.render_item(ix, window, cx).map(|item| {
|
||||||
|
item.selected(selected)
|
||||||
|
.secondary_selected(mouse_right_clicked)
|
||||||
|
}))
|
||||||
.when(self.selectable, |this| {
|
.when(self.selectable, |this| {
|
||||||
this.when(selected || right_clicked, |this| {
|
this.on_mouse_down(
|
||||||
this.child(
|
|
||||||
div()
|
|
||||||
.absolute()
|
|
||||||
.top(px(0.))
|
|
||||||
.left(px(0.))
|
|
||||||
.right(px(0.))
|
|
||||||
.bottom(px(0.))
|
|
||||||
.when(selected, |this| this.bg(cx.theme().list_active))
|
|
||||||
.border_1()
|
|
||||||
.border_color(cx.theme().list_active_border),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.on_mouse_down(
|
|
||||||
MouseButton::Left,
|
MouseButton::Left,
|
||||||
cx.listener(move |this, ev: &MouseDownEvent, window, cx| {
|
cx.listener(move |this, ev: &MouseDownEvent, window, cx| {
|
||||||
this.right_clicked_index = None;
|
this.mouse_right_clicked_index = None;
|
||||||
this.selected_index = Some(ix);
|
this.selected_index = Some(ix);
|
||||||
this.on_action_confirm(
|
this.on_action_confirm(
|
||||||
&Confirm {
|
&Confirm {
|
||||||
|
|
@ -538,12 +545,55 @@ where
|
||||||
.on_mouse_down(
|
.on_mouse_down(
|
||||||
MouseButton::Right,
|
MouseButton::Right,
|
||||||
cx.listener(move |this, _, _, cx| {
|
cx.listener(move |this, _, _, cx| {
|
||||||
this.right_clicked_index = Some(ix);
|
this.mouse_right_clicked_index = Some(ix);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_items(
|
||||||
|
&mut self,
|
||||||
|
items_count: usize,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> impl IntoElement {
|
||||||
|
let sizing_behavior = if self.max_height.is_some() {
|
||||||
|
ListSizingBehavior::Infer
|
||||||
|
} else {
|
||||||
|
ListSizingBehavior::Auto
|
||||||
|
};
|
||||||
|
|
||||||
|
v_flex()
|
||||||
|
.flex_grow()
|
||||||
|
.relative()
|
||||||
|
.when_some(self.max_height, |this, h| this.max_h(h))
|
||||||
|
.overflow_hidden()
|
||||||
|
.when(items_count == 0, |this| {
|
||||||
|
this.child(self.delegate().render_empty(window, cx))
|
||||||
|
})
|
||||||
|
.when(items_count > 0, |this| {
|
||||||
|
this.child(
|
||||||
|
uniform_list(
|
||||||
|
"uniform-list",
|
||||||
|
items_count,
|
||||||
|
cx.processor(move |list, visible_range: Range<usize>, window, cx| {
|
||||||
|
list.load_more_if_need(items_count, visible_range.end, window, cx);
|
||||||
|
|
||||||
|
visible_range
|
||||||
|
.map(|ix| list.render_list_item(ix, window, cx))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.flex_grow()
|
||||||
|
.paddings(self.paddings)
|
||||||
|
.with_sizing_behavior(sizing_behavior)
|
||||||
|
.track_scroll(self.vertical_scroll_handle.clone())
|
||||||
|
.into_any_element(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.children(self.render_scrollbar(window, cx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<D> Focusable for List<D>
|
impl<D> Focusable for List<D>
|
||||||
|
|
@ -564,14 +614,8 @@ where
|
||||||
D: ListDelegate,
|
D: ListDelegate,
|
||||||
{
|
{
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let vertical_scroll_handle = self.vertical_scroll_handle.clone();
|
|
||||||
let items_count = self.delegate.items_count(cx);
|
let items_count = self.delegate.items_count(cx);
|
||||||
let loading = self.delegate.loading(cx);
|
let loading = self.delegate.loading(cx);
|
||||||
let sizing_behavior = if self.max_height.is_some() {
|
|
||||||
ListSizingBehavior::Infer
|
|
||||||
} else {
|
|
||||||
ListSizingBehavior::Auto
|
|
||||||
};
|
|
||||||
|
|
||||||
let initial_view = if let Some(input) = &self.query_input {
|
let initial_view = if let Some(input) = &self.query_input {
|
||||||
if input.read(cx).value().is_empty() {
|
if input.read(cx).value().is_empty() {
|
||||||
|
|
@ -624,53 +668,13 @@ where
|
||||||
if let Some(view) = initial_view {
|
if let Some(view) = initial_view {
|
||||||
this.child(view)
|
this.child(view)
|
||||||
} else {
|
} else {
|
||||||
this.child(
|
this.child(self.render_items(items_count, window, cx))
|
||||||
v_flex()
|
|
||||||
.flex_grow()
|
|
||||||
.relative()
|
|
||||||
.when_some(self.max_height, |this, h| this.max_h(h))
|
|
||||||
.overflow_hidden()
|
|
||||||
.when(items_count == 0, |this| {
|
|
||||||
this.child(self.delegate().render_empty(window, cx))
|
|
||||||
})
|
|
||||||
.when(items_count > 0, |this| {
|
|
||||||
this.child(
|
|
||||||
uniform_list(
|
|
||||||
"uniform-list",
|
|
||||||
items_count,
|
|
||||||
cx.processor(
|
|
||||||
move |list, visible_range: Range<usize>, window, cx| {
|
|
||||||
list.load_more_if_need(
|
|
||||||
items_count,
|
|
||||||
visible_range.end,
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
|
|
||||||
visible_range
|
|
||||||
.map(|ix| {
|
|
||||||
list.render_list_item(
|
|
||||||
ix, window, cx,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.flex_grow()
|
|
||||||
.with_sizing_behavior(sizing_behavior)
|
|
||||||
.track_scroll(vertical_scroll_handle)
|
|
||||||
.into_any_element(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.children(self.render_scrollbar(window, cx)),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Click out to cancel right clicked row
|
// Click out to cancel right clicked row
|
||||||
.when(self.right_clicked_index.is_some(), |this| {
|
.when(self.mouse_right_clicked_index.is_some(), |this| {
|
||||||
this.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
this.on_mouse_down_out(cx.listener(|this, _, _, cx| {
|
||||||
this.right_clicked_index = None;
|
this.mouse_right_clicked_index = None;
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,18 @@
|
||||||
use crate::{h_flex, ActiveTheme, Disableable, Icon, Selectable, Sizable as _};
|
use crate::{h_flex, ActiveTheme, Disableable, Icon, Selectable, Sizable as _, StyledExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, Div, ElementId,
|
div, prelude::FluentBuilder as _, AnyElement, App, ClickEvent, Div, ElementId,
|
||||||
InteractiveElement, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce,
|
InteractiveElement, IntoElement, MouseButton, MouseMoveEvent, ParentElement, RenderOnce,
|
||||||
Stateful, StatefulInteractiveElement as _, Styled, Window,
|
Stateful, StatefulInteractiveElement as _, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
id: ElementId,
|
|
||||||
base: Stateful<Div>,
|
base: Stateful<Div>,
|
||||||
|
style: StyleRefinement,
|
||||||
disabled: bool,
|
disabled: bool,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
|
secondary_selected: bool,
|
||||||
confirmed: bool,
|
confirmed: bool,
|
||||||
check_icon: Option<Icon>,
|
check_icon: Option<Icon>,
|
||||||
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
|
||||||
|
|
@ -24,10 +25,11 @@ impl ListItem {
|
||||||
pub fn new(id: impl Into<ElementId>) -> Self {
|
pub fn new(id: impl Into<ElementId>) -> Self {
|
||||||
let id: ElementId = id.into();
|
let id: ElementId = id.into();
|
||||||
Self {
|
Self {
|
||||||
id: id.clone(),
|
base: h_flex().id(id),
|
||||||
base: h_flex().id(id).gap_x_1().py_1().px_2().text_base(),
|
style: StyleRefinement::default(),
|
||||||
disabled: false,
|
disabled: false,
|
||||||
selected: false,
|
selected: false,
|
||||||
|
secondary_selected: false,
|
||||||
confirmed: false,
|
confirmed: false,
|
||||||
on_click: None,
|
on_click: None,
|
||||||
on_mouse_enter: None,
|
on_mouse_enter: None,
|
||||||
|
|
@ -97,10 +99,6 @@ impl Disableable for ListItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for ListItem {
|
impl Selectable for ListItem {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self
|
self
|
||||||
|
|
@ -109,11 +107,16 @@ impl Selectable for ListItem {
|
||||||
fn is_selected(&self) -> bool {
|
fn is_selected(&self) -> bool {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn secondary_selected(mut self, selected: bool) -> Self {
|
||||||
|
self.secondary_selected = selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Styled for ListItem {
|
impl Styled for ListItem {
|
||||||
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
fn style(&mut self) -> &mut gpui::StyleRefinement {
|
||||||
self.base.style()
|
&mut self.style
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -127,11 +130,22 @@ impl RenderOnce for ListItem {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let is_active = self.confirmed || self.selected;
|
let is_active = self.confirmed || self.selected;
|
||||||
|
|
||||||
|
let corner_radii = self.style.corner_radii.clone();
|
||||||
|
|
||||||
|
let mut selected_style = StyleRefinement::default();
|
||||||
|
selected_style.corner_radii = corner_radii;
|
||||||
|
|
||||||
self.base
|
self.base
|
||||||
|
.relative()
|
||||||
|
.gap_x_1()
|
||||||
|
.py_1()
|
||||||
|
.px_3()
|
||||||
|
.text_base()
|
||||||
.text_color(cx.theme().foreground)
|
.text_color(cx.theme().foreground)
|
||||||
.relative()
|
.relative()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
|
.refine_style(&self.style)
|
||||||
.when(!self.disabled, |this| {
|
.when(!self.disabled, |this| {
|
||||||
this.when_some(self.on_click, |this, on_click| {
|
this.when_some(self.on_click, |this, on_click| {
|
||||||
this.on_mouse_down(MouseButton::Left, move |_, _, cx| {
|
this.on_mouse_down(MouseButton::Left, move |_, _, cx| {
|
||||||
|
|
@ -168,5 +182,25 @@ impl RenderOnce for ListItem {
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))
|
.when_some(self.suffix, |this, suffix| this.child(suffix(window, cx)))
|
||||||
|
.map(|this| {
|
||||||
|
if self.selected || self.secondary_selected {
|
||||||
|
this.bg(cx.theme().accent).child(
|
||||||
|
div()
|
||||||
|
.absolute()
|
||||||
|
.top_0()
|
||||||
|
.left_0()
|
||||||
|
.right_0()
|
||||||
|
.bottom_0()
|
||||||
|
.when(!self.secondary_selected, |this| {
|
||||||
|
this.bg(cx.theme().list_active)
|
||||||
|
})
|
||||||
|
.border_1()
|
||||||
|
.border_color(cx.theme().list_active_border)
|
||||||
|
.refine_style(&selected_style),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,6 @@ impl Disableable for MenuItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for MenuItem {
|
impl Selectable for MenuItem {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ pub fn init(cx: &mut App) {
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
pub trait PopupMenuExt: Styled + Selectable + InteractiveElement + IntoElement + 'static {
|
||||||
/// Create a popup menu with the given items, anchored to the TopLeft corner
|
/// Create a popup menu with the given items, anchored to the TopLeft corner
|
||||||
fn popup_menu(
|
fn popup_menu(
|
||||||
self,
|
self,
|
||||||
|
|
@ -44,9 +44,9 @@ pub trait PopupMenuExt: Styled + Selectable + IntoElement + 'static {
|
||||||
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
f: impl Fn(PopupMenu, &mut Window, &mut Context<PopupMenu>) -> PopupMenu + 'static,
|
||||||
) -> Popover<PopupMenu> {
|
) -> Popover<PopupMenu> {
|
||||||
let style = self.style().clone();
|
let style = self.style().clone();
|
||||||
let element_id = self.element_id();
|
let id = self.interactivity().element_id.clone();
|
||||||
|
|
||||||
Popover::new(SharedString::from(format!("popup-menu:{:?}", element_id)))
|
Popover::new(SharedString::from(format!("popup-menu:{:?}", id)))
|
||||||
.no_style()
|
.no_style()
|
||||||
.trigger(self)
|
.trigger(self)
|
||||||
.trigger_style(style)
|
.trigger_style(style)
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ impl PopoverContent {
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
style: StyleRefinement::default().p_2(),
|
style: StyleRefinement::default(),
|
||||||
focus_handle,
|
focus_handle,
|
||||||
content: Rc::new(content),
|
content: Rc::new(content),
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +52,7 @@ impl Styled for PopoverContent {
|
||||||
impl Render for PopoverContent {
|
impl Render for PopoverContent {
|
||||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
|
.p_2()
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.key_context(CONTEXT)
|
.key_context(CONTEXT)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
|
prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
RenderOnce, SharedString, Styled,
|
Styled,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{h_flex, popup_menu::PopupMenuExt, ActiveTheme as _, Collapsible, Selectable};
|
use crate::{h_flex, popup_menu::PopupMenuExt, ActiveTheme as _, Collapsible, Selectable};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct SidebarFooter {
|
pub struct SidebarFooter {
|
||||||
id: ElementId,
|
|
||||||
base: Div,
|
base: Div,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
|
|
@ -16,7 +15,6 @@ pub struct SidebarFooter {
|
||||||
impl SidebarFooter {
|
impl SidebarFooter {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: SharedString::from("sidebar-footer").into(),
|
|
||||||
base: h_flex().gap_2().w_full(),
|
base: h_flex().gap_2().w_full(),
|
||||||
selected: false,
|
selected: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
|
|
@ -29,14 +27,11 @@ impl Selectable for SidebarFooter {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_id(&self) -> &gpui::ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_selected(&self) -> bool {
|
fn is_selected(&self) -> bool {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Collapsible for SidebarFooter {
|
impl Collapsible for SidebarFooter {
|
||||||
fn is_collapsed(&self) -> bool {
|
fn is_collapsed(&self) -> bool {
|
||||||
self.collapsed
|
self.collapsed
|
||||||
|
|
@ -57,11 +52,18 @@ impl Styled for SidebarFooter {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InteractiveElement for SidebarFooter {
|
||||||
|
fn interactivity(&mut self) -> &mut gpui::Interactivity {
|
||||||
|
self.base.interactivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PopupMenuExt for SidebarFooter {}
|
impl PopupMenuExt for SidebarFooter {}
|
||||||
impl RenderOnce for SidebarFooter {
|
impl RenderOnce for SidebarFooter {
|
||||||
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id("sidebar-footer")
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_2()
|
.p_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
use gpui::{
|
use gpui::{
|
||||||
prelude::FluentBuilder as _, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
|
prelude::FluentBuilder as _, Div, InteractiveElement, IntoElement, ParentElement, RenderOnce,
|
||||||
RenderOnce, SharedString, Styled,
|
Styled,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{h_flex, popup_menu::PopupMenuExt, ActiveTheme as _, Collapsible, Selectable};
|
use crate::{h_flex, popup_menu::PopupMenuExt, ActiveTheme as _, Collapsible, Selectable};
|
||||||
|
|
||||||
#[derive(IntoElement)]
|
#[derive(IntoElement)]
|
||||||
pub struct SidebarHeader {
|
pub struct SidebarHeader {
|
||||||
id: ElementId,
|
|
||||||
base: Div,
|
base: Div,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
collapsed: bool,
|
collapsed: bool,
|
||||||
|
|
@ -16,7 +15,6 @@ pub struct SidebarHeader {
|
||||||
impl SidebarHeader {
|
impl SidebarHeader {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: SharedString::from("sidebar-header").into(),
|
|
||||||
base: h_flex().gap_2().w_full(),
|
base: h_flex().gap_2().w_full(),
|
||||||
selected: false,
|
selected: false,
|
||||||
collapsed: false,
|
collapsed: false,
|
||||||
|
|
@ -29,10 +27,6 @@ impl Selectable for SidebarHeader {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_id(&self) -> &gpui::ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_selected(&self) -> bool {
|
fn is_selected(&self) -> bool {
|
||||||
self.selected
|
self.selected
|
||||||
}
|
}
|
||||||
|
|
@ -58,11 +52,18 @@ impl Styled for SidebarHeader {
|
||||||
self.base.style()
|
self.base.style()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl InteractiveElement for SidebarHeader {
|
||||||
|
fn interactivity(&mut self) -> &mut gpui::Interactivity {
|
||||||
|
self.base.interactivity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PopupMenuExt for SidebarHeader {}
|
impl PopupMenuExt for SidebarHeader {}
|
||||||
impl RenderOnce for SidebarHeader {
|
impl RenderOnce for SidebarHeader {
|
||||||
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
fn render(self, _: &mut gpui::Window, cx: &mut gpui::App) -> impl gpui::IntoElement {
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id("sidebar-header")
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.p_2()
|
.p_2()
|
||||||
.w_full()
|
.w_full()
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ use crate::{
|
||||||
ActiveTheme,
|
ActiveTheme,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, point, px, App, Axis, BoxShadow, DefiniteLength, Div, Edges, Element, ElementId,
|
div, point, px, App, Axis, BoxShadow, DefiniteLength, Div, Edges, Element, FocusHandle, Hsla,
|
||||||
FocusHandle, Hsla, Pixels, Refineable, StyleRefinement, Styled, Window,
|
Pixels, Refineable, StyleRefinement, Styled, Window,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
@ -338,14 +338,16 @@ impl From<Pixels> for Size {
|
||||||
|
|
||||||
/// A trait for defining element that can be selected.
|
/// A trait for defining element that can be selected.
|
||||||
pub trait Selectable: Sized {
|
pub trait Selectable: Sized {
|
||||||
/// Returns the element id of the element.
|
|
||||||
fn element_id(&self) -> &ElementId;
|
|
||||||
|
|
||||||
/// Set the selected state of the element.
|
/// Set the selected state of the element.
|
||||||
fn selected(self, selected: bool) -> Self;
|
fn selected(self, selected: bool) -> Self;
|
||||||
|
|
||||||
/// Returns true if the element is selected.
|
/// Returns true if the element is selected.
|
||||||
fn is_selected(&self) -> bool;
|
fn is_selected(&self) -> bool;
|
||||||
|
|
||||||
|
/// Set is the element mouse right clicked, default do nothing.
|
||||||
|
fn secondary_selected(self, _: bool) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for defining element that can be disabled.
|
/// A trait for defining element that can be disabled.
|
||||||
|
|
|
||||||
|
|
@ -533,10 +533,6 @@ impl ParentElement for Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Selectable for Tab {
|
impl Selectable for Tab {
|
||||||
fn element_id(&self) -> &ElementId {
|
|
||||||
&self.id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected(mut self, selected: bool) -> Self {
|
fn selected(mut self, selected: bool) -> Self {
|
||||||
self.selected = selected;
|
self.selected = selected;
|
||||||
self
|
self
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue