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:
parent
f20f573dbc
commit
2da13499e5
2 changed files with 122 additions and 61 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue