list: Add render_initial support to List. (#258)

Add a method to the List component to render the initial state. When
input is empty, you may need to render a special interface to the user.
This method can be called at this point

### Showcase

https://github.com/user-attachments/assets/39c61bfe-4451-43f9-b0d9-8f941ebd94dd
This commit is contained in:
ihavecoke 2024-09-21 01:53:32 +08:00 committed by GitHub
parent f20f573dbc
commit 2da13499e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 122 additions and 61 deletions

View file

@ -2,16 +2,15 @@ use core::time;
use fake::Fake; use fake::Fake;
use gpui::{ use gpui::{
actions, div, px, ElementId, FocusHandle, FocusableView, InteractiveElement, IntoElement, actions, div, px, relative, AnyElement, ElementId, FocusHandle, FocusableView,
ParentElement, Render, RenderOnce, Styled, Task, Timer, View, ViewContext, VisualContext, InteractiveElement, IntoElement, ParentElement, Render, RenderOnce, Styled, Task, Timer, View,
WindowContext, ViewContext, VisualContext, WindowContext,
}; };
use ui::{ use ui::{
h_flex, h_flex,
label::Label, label::Label,
list::ListItem, list::{List, ListDelegate, ListItem},
list::{List, ListDelegate},
theme::{hsl, ActiveTheme}, theme::{hsl, ActiveTheme},
v_flex, v_flex,
}; };
@ -167,6 +166,37 @@ impl ListDelegate for CompanyListDelegate {
} }
} }
fn render_initial(&self, cx: &mut ViewContext<List<Self>>) -> Option<AnyElement> {
let histories = ["BABA", "BIDU", "GOOGL", "LB", "LP", "LBW"];
let input_history = histories
.into_iter()
.map(|name| {
div()
.rounded_xl()
.min_w(px(30.))
.border_1()
.rounded_md()
.border_color(cx.theme().muted_foreground.opacity(0.3))
.line_height(relative(1.))
.p_1()
.child(div().child(name).text_xs())
})
.collect::<Vec<_>>();
let element = v_flex()
.p_4()
.child(
v_flex()
.gap_y_2()
.child("History")
.child(h_flex().gap_x_4().children(input_history)),
)
.into_any_element();
Some(element)
}
fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) { fn set_selected_index(&mut self, ix: Option<usize>, cx: &mut ViewContext<List<Self>>) {
if let Some(ix) = ix { if let Some(ix) = ix {
self.selected_index = ix; self.selected_index = ix;

View file

@ -1,17 +1,18 @@
use std::time::Duration; use std::time::Duration;
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
use crate::input::{InputEvent, TextInput}; use crate::{
use crate::scroll::ScrollbarState; input::{InputEvent, TextInput},
use crate::theme::ActiveTheme; scroll::{Scrollbar, ScrollbarState},
use crate::{scroll::Scrollbar, v_flex}; theme::ActiveTheme,
use crate::{IconName, Size}; v_flex, IconName, Size,
use gpui::{ };
actions, div, prelude::FluentBuilder, uniform_list, AppContext, FocusHandle, FocusableView, use gpui::{
InteractiveElement, IntoElement, KeyBinding, Length, ListSizingBehavior, MouseButton, actions, div, prelude::FluentBuilder, uniform_list, AnyElement, AppContext, Entity,
ParentElement, Render, Styled, Task, UniformListScrollHandle, View, ViewContext, VisualContext, FocusHandle, FocusableView, InteractiveElement, IntoElement, KeyBinding, Length,
ListSizingBehavior, MouseButton, ParentElement, Render, SharedString, Styled, Task,
UniformListScrollHandle, View, ViewContext, VisualContext, WindowContext,
}; };
use gpui::{Entity, SharedString, WindowContext};
use smol::Timer; use smol::Timer;
actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]); actions!(list, [Cancel, Confirm, SelectPrev, SelectNext]);
@ -50,6 +51,17 @@ pub trait ListDelegate: Sized + 'static {
div() div()
} }
/// 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.
///
/// For example: The last search results, or the last selected item.
///
/// Default is None, that means no initial state.
fn render_initial(&self, cx: &mut ViewContext<List<Self>>) -> Option<AnyElement> {
None
}
/// Return the confirmed index of the selected item. /// Return the confirmed index of the selected item.
fn confirmed_index(&self) -> Option<usize> { fn confirmed_index(&self) -> Option<usize> {
None None
@ -322,6 +334,16 @@ where
let selected_bg = cx.theme().list_active; let selected_bg = cx.theme().list_active;
let inital_view = if let Some(input) = &self.query_input {
if input.read(cx).text().is_empty() {
self.delegate().render_initial(cx)
} else {
None
}
} else {
None
};
v_flex() v_flex()
.key_context("List") .key_context("List")
.id("list") .id("list")
@ -345,52 +367,61 @@ where
.child(input), .child(input),
) )
}) })
.child( .map(|this| {
v_flex() if let Some(view) = inital_view {
.flex_grow() this.child(view)
.relative() } else {
.when_some(self.max_height, |this, h| this.max_h(h)) this.child(
.overflow_hidden() v_flex()
.when(items_count == 0, |this| {
this.child(self.delegate().render_empty(cx))
})
.when(items_count > 0, |this| {
this.child(
uniform_list(view, "uniform-list", items_count, {
move |list, visible_range, cx| {
visible_range
.map(|ix| {
div()
.id("list-item")
.w_full()
.children(list.delegate.render_item(ix, cx))
.when_some(
list.selected_index,
|this, selected_index| {
this.when(ix == selected_index, |this| {
this.bg(selected_bg)
})
},
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, cx| {
cx.stop_propagation();
this.selected_index = Some(ix);
this.on_action_confirm(&Confirm, cx);
}),
)
})
.collect::<Vec<_>>()
}
})
.flex_grow() .flex_grow()
.with_sizing_behavior(sizing_behavior) .relative()
.track_scroll(vertical_scroll_handle) .when_some(self.max_height, |this, h| this.max_h(h))
.into_any_element(), .overflow_hidden()
) .when(items_count == 0, |this| {
}) this.child(self.delegate().render_empty(cx))
.children(self.render_scrollbar(cx)), })
) .when(items_count > 0, |this| {
this.child(
uniform_list(view, "uniform-list", items_count, {
move |list, visible_range, cx| {
visible_range
.map(|ix| {
div()
.id("list-item")
.w_full()
.children(list.delegate.render_item(ix, cx))
.when_some(
list.selected_index,
|this, selected_index| {
this.when(
ix == selected_index,
|this| this.bg(selected_bg),
)
},
)
.on_mouse_down(
MouseButton::Left,
cx.listener(move |this, _, cx| {
cx.stop_propagation();
this.selected_index = Some(ix);
this.on_action_confirm(
&Confirm, cx,
);
}),
)
})
.collect::<Vec<_>>()
}
})
.flex_grow()
.with_sizing_behavior(sizing_behavior)
.track_scroll(vertical_scroll_handle)
.into_any_element(),
)
})
.children(self.render_scrollbar(cx)),
)
}
})
} }
} }