Simplify ListState
API (#35685)
Follow up to: https://github.com/zed-industries/zed/pull/35670, simplifies the List state APIs so you no longer have to worry about strong vs. weak pointers when rendering list items. Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
parent
d0de81b0b4
commit
53175263a1
15 changed files with 322 additions and 403 deletions
|
@ -173,23 +173,7 @@ impl AcpThreadView {
|
||||||
|
|
||||||
let mention_set = mention_set.clone();
|
let mention_set = mention_set.clone();
|
||||||
|
|
||||||
let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0), {
|
let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0));
|
||||||
let this = cx.entity().downgrade();
|
|
||||||
move |index: usize, window, cx| {
|
|
||||||
let Some(this) = this.upgrade() else {
|
|
||||||
return Empty.into_any();
|
|
||||||
};
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
let Some((entry, len)) = this.thread().and_then(|thread| {
|
|
||||||
let entries = &thread.read(cx).entries();
|
|
||||||
Some((entries.get(index)?, entries.len()))
|
|
||||||
}) else {
|
|
||||||
return Empty.into_any();
|
|
||||||
};
|
|
||||||
this.render_entry(index, len, entry, window, cx)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
agent: agent.clone(),
|
agent: agent.clone(),
|
||||||
|
@ -2552,10 +2536,21 @@ impl Render for AcpThreadView {
|
||||||
v_flex().flex_1().map(|this| {
|
v_flex().flex_1().map(|this| {
|
||||||
if self.list_state.item_count() > 0 {
|
if self.list_state.item_count() > 0 {
|
||||||
this.child(
|
this.child(
|
||||||
list(self.list_state.clone())
|
list(
|
||||||
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
self.list_state.clone(),
|
||||||
.flex_grow()
|
cx.processor(|this, index: usize, window, cx| {
|
||||||
.into_any(),
|
let Some((entry, len)) = this.thread().and_then(|thread| {
|
||||||
|
let entries = &thread.read(cx).entries();
|
||||||
|
Some((entries.get(index)?, entries.len()))
|
||||||
|
}) else {
|
||||||
|
return Empty.into_any();
|
||||||
|
};
|
||||||
|
this.render_entry(index, len, entry, window, cx)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
||||||
|
.flex_grow()
|
||||||
|
.into_any(),
|
||||||
)
|
)
|
||||||
.children(match thread_clone.read(cx).status() {
|
.children(match thread_clone.read(cx).status() {
|
||||||
ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
|
ThreadStatus::Idle | ThreadStatus::WaitingForToolConfirmation => {
|
||||||
|
|
|
@ -780,13 +780,7 @@ impl ActiveThread {
|
||||||
cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
|
cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), {
|
let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.));
|
||||||
let this = cx.entity().downgrade();
|
|
||||||
move |ix, window: &mut Window, cx: &mut App| {
|
|
||||||
this.update(cx, |this, cx| this.render_message(ix, window, cx))
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let workspace_subscription = if let Some(workspace) = workspace.upgrade() {
|
let workspace_subscription = if let Some(workspace) = workspace.upgrade() {
|
||||||
Some(cx.observe_release(&workspace, |this, _, cx| {
|
Some(cx.observe_release(&workspace, |this, _, cx| {
|
||||||
|
@ -1846,7 +1840,12 @@ impl ActiveThread {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
|
fn render_message(
|
||||||
|
&mut self,
|
||||||
|
ix: usize,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) -> AnyElement {
|
||||||
let message_id = self.messages[ix];
|
let message_id = self.messages[ix];
|
||||||
let workspace = self.workspace.clone();
|
let workspace = self.workspace.clone();
|
||||||
let thread = self.thread.read(cx);
|
let thread = self.thread.read(cx);
|
||||||
|
@ -3613,7 +3612,7 @@ impl Render for ActiveThread {
|
||||||
this.hide_scrollbar_later(cx);
|
this.hide_scrollbar_later(cx);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.child(list(self.list_state.clone()).flex_grow())
|
.child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
|
||||||
.when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
|
.when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
|
||||||
this.child(scrollbar)
|
this.child(scrollbar)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1471,7 +1471,6 @@ impl AgentPanel {
|
||||||
|
|
||||||
let current_is_special = current_is_history || current_is_config;
|
let current_is_special = current_is_history || current_is_config;
|
||||||
let new_is_special = new_is_history || new_is_config;
|
let new_is_special = new_is_history || new_is_config;
|
||||||
let mut old_acp_thread = None;
|
|
||||||
|
|
||||||
match &self.active_view {
|
match &self.active_view {
|
||||||
ActiveView::Thread { thread, .. } => {
|
ActiveView::Thread { thread, .. } => {
|
||||||
|
@ -1483,9 +1482,6 @@ impl AgentPanel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ActiveView::ExternalAgentThread { thread_view } => {
|
|
||||||
old_acp_thread.replace(thread_view.downgrade());
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1516,11 +1512,6 @@ impl AgentPanel {
|
||||||
self.active_view = new_view;
|
self.active_view = new_view;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert!(
|
|
||||||
old_acp_thread.map_or(true, |thread| !thread.is_upgradable()),
|
|
||||||
"AcpThreadView leaked"
|
|
||||||
);
|
|
||||||
|
|
||||||
self.acp_message_history.borrow_mut().reset_position();
|
self.acp_message_history.borrow_mut().reset_position();
|
||||||
|
|
||||||
self.focus_handle(cx).focus(window);
|
self.focus_handle(cx).focus(window);
|
||||||
|
|
|
@ -103,28 +103,16 @@ impl ChatPanel {
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let entity = cx.entity().downgrade();
|
let message_list = ListState::new(0, gpui::ListAlignment::Bottom, px(1000.));
|
||||||
let message_list = ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Bottom,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
if let Some(entity) = entity.upgrade() {
|
|
||||||
entity.update(cx, |this: &mut Self, cx| {
|
|
||||||
this.render_message(ix, window, cx).into_any_element()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
div().into_any()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
message_list.set_scroll_handler(cx.listener(|this, event: &ListScrollEvent, _, cx| {
|
message_list.set_scroll_handler(cx.listener(
|
||||||
if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
|this: &mut Self, event: &ListScrollEvent, _, cx| {
|
||||||
this.load_more_messages(cx);
|
if event.visible_range.start < MESSAGE_LOADING_THRESHOLD {
|
||||||
}
|
this.load_more_messages(cx);
|
||||||
this.is_scrolled_to_bottom = !event.is_scrolled;
|
}
|
||||||
}));
|
this.is_scrolled_to_bottom = !event.is_scrolled;
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
let local_offset = chrono::Local::now().offset().local_minus_utc();
|
let local_offset = chrono::Local::now().offset().local_minus_utc();
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
@ -399,7 +387,7 @@ impl ChatPanel {
|
||||||
ix: usize,
|
ix: usize,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> AnyElement {
|
||||||
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
let active_chat = &self.active_chat.as_ref().unwrap().0;
|
||||||
let (message, is_continuation_from_previous, is_admin) =
|
let (message, is_continuation_from_previous, is_admin) =
|
||||||
active_chat.update(cx, |active_chat, cx| {
|
active_chat.update(cx, |active_chat, cx| {
|
||||||
|
@ -582,6 +570,7 @@ impl ChatPanel {
|
||||||
self.render_popover_buttons(message_id, can_delete_message, can_edit_message, cx)
|
self.render_popover_buttons(message_id, can_delete_message, can_edit_message, cx)
|
||||||
.mt_neg_2p5(),
|
.mt_neg_2p5(),
|
||||||
)
|
)
|
||||||
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_open_menu(&self, message_id: Option<u64>) -> bool {
|
fn has_open_menu(&self, message_id: Option<u64>) -> bool {
|
||||||
|
@ -979,7 +968,13 @@ impl Render for ChatPanel {
|
||||||
)
|
)
|
||||||
.child(div().flex_grow().px_2().map(|this| {
|
.child(div().flex_grow().px_2().map(|this| {
|
||||||
if self.active_chat.is_some() {
|
if self.active_chat.is_some() {
|
||||||
this.child(list(self.message_list.clone()).size_full())
|
this.child(
|
||||||
|
list(
|
||||||
|
self.message_list.clone(),
|
||||||
|
cx.processor(Self::render_message),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
|
|
|
@ -324,20 +324,6 @@ impl CollabPanel {
|
||||||
)
|
)
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let entity = cx.entity().downgrade();
|
|
||||||
let list_state = ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
if let Some(entity) = entity.upgrade() {
|
|
||||||
entity.update(cx, |this, cx| this.render_list_entry(ix, window, cx))
|
|
||||||
} else {
|
|
||||||
div().into_any()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
width: None,
|
width: None,
|
||||||
focus_handle: cx.focus_handle(),
|
focus_handle: cx.focus_handle(),
|
||||||
|
@ -345,7 +331,7 @@ impl CollabPanel {
|
||||||
fs: workspace.app_state().fs.clone(),
|
fs: workspace.app_state().fs.clone(),
|
||||||
pending_serialization: Task::ready(None),
|
pending_serialization: Task::ready(None),
|
||||||
context_menu: None,
|
context_menu: None,
|
||||||
list_state,
|
list_state: ListState::new(0, gpui::ListAlignment::Top, px(1000.)),
|
||||||
channel_name_editor,
|
channel_name_editor,
|
||||||
filter_editor,
|
filter_editor,
|
||||||
entries: Vec::default(),
|
entries: Vec::default(),
|
||||||
|
@ -2431,7 +2417,13 @@ impl CollabPanel {
|
||||||
});
|
});
|
||||||
v_flex()
|
v_flex()
|
||||||
.size_full()
|
.size_full()
|
||||||
.child(list(self.list_state.clone()).size_full())
|
.child(
|
||||||
|
list(
|
||||||
|
self.list_state.clone(),
|
||||||
|
cx.processor(Self::render_list_entry),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
.child(
|
.child(
|
||||||
v_flex()
|
v_flex()
|
||||||
.child(div().mx_2().border_primary(cx).border_t_1())
|
.child(div().mx_2().border_primary(cx).border_t_1())
|
||||||
|
|
|
@ -118,16 +118,7 @@ impl NotificationPanel {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let entity = cx.entity().downgrade();
|
let notification_list = ListState::new(0, ListAlignment::Top, px(1000.));
|
||||||
let notification_list =
|
|
||||||
ListState::new(0, ListAlignment::Top, px(1000.), move |ix, window, cx| {
|
|
||||||
entity
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|entity| {
|
|
||||||
entity.update(cx, |this, cx| this.render_notification(ix, window, cx))
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| div().into_any())
|
|
||||||
});
|
|
||||||
notification_list.set_scroll_handler(cx.listener(
|
notification_list.set_scroll_handler(cx.listener(
|
||||||
|this, event: &ListScrollEvent, _, cx| {
|
|this, event: &ListScrollEvent, _, cx| {
|
||||||
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
|
if event.count.saturating_sub(event.visible_range.end) < LOADING_THRESHOLD {
|
||||||
|
@ -687,7 +678,16 @@ impl Render for NotificationPanel {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
this.child(list(self.notification_list.clone()).size_full())
|
this.child(
|
||||||
|
list(
|
||||||
|
self.notification_list.clone(),
|
||||||
|
cx.processor(|this, ix, window, cx| {
|
||||||
|
this.render_notification(ix, window, cx)
|
||||||
|
.unwrap_or_else(|| div().into_any())
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,22 +13,8 @@ pub(crate) struct LoadedSourceList {
|
||||||
|
|
||||||
impl LoadedSourceList {
|
impl LoadedSourceList {
|
||||||
pub fn new(session: Entity<Session>, cx: &mut Context<Self>) -> Self {
|
pub fn new(session: Entity<Session>, cx: &mut Context<Self>) -> Self {
|
||||||
let weak_entity = cx.weak_entity();
|
|
||||||
let focus_handle = cx.focus_handle();
|
let focus_handle = cx.focus_handle();
|
||||||
|
let list = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||||
let list = ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, _window, cx| {
|
|
||||||
weak_entity
|
|
||||||
.upgrade()
|
|
||||||
.map(|loaded_sources| {
|
|
||||||
loaded_sources.update(cx, |this, cx| this.render_entry(ix, cx))
|
|
||||||
})
|
|
||||||
.unwrap_or(div().into_any())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
let _subscription = cx.subscribe(&session, |this, _, event, cx| match event {
|
||||||
SessionEvent::Stopped(_) | SessionEvent::LoadedSources => {
|
SessionEvent::Stopped(_) | SessionEvent::LoadedSources => {
|
||||||
|
@ -98,6 +84,12 @@ impl Render for LoadedSourceList {
|
||||||
.track_focus(&self.focus_handle)
|
.track_focus(&self.focus_handle)
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_1()
|
.p_1()
|
||||||
.child(list(self.list.clone()).size_full())
|
.child(
|
||||||
|
list(
|
||||||
|
self.list.clone(),
|
||||||
|
cx.processor(|this, ix, _window, cx| this.render_entry(ix, cx)),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,13 +70,7 @@ impl StackFrameList {
|
||||||
_ => {}
|
_ => {}
|
||||||
});
|
});
|
||||||
|
|
||||||
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.), {
|
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||||
let this = cx.weak_entity();
|
|
||||||
move |ix, _window, cx| {
|
|
||||||
this.update(cx, |this, cx| this.render_entry(ix, cx))
|
|
||||||
.unwrap_or(div().into_any())
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let scrollbar_state = ScrollbarState::new(list_state.clone());
|
let scrollbar_state = ScrollbarState::new(list_state.clone());
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
|
@ -708,11 +702,14 @@ impl StackFrameList {
|
||||||
self.activate_selected_entry(window, cx);
|
self.activate_selected_entry(window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_list(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
fn render_list(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div()
|
div().p_1().size_full().child(
|
||||||
.p_1()
|
list(
|
||||||
.size_full()
|
self.list_state.clone(),
|
||||||
.child(list(self.list_state.clone()).size_full())
|
cx.processor(|this, ix, _window, cx| this.render_entry(ix, cx)),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,16 @@ use refineable::Refineable as _;
|
||||||
use std::{cell::RefCell, ops::Range, rc::Rc};
|
use std::{cell::RefCell, ops::Range, rc::Rc};
|
||||||
use sum_tree::{Bias, Dimensions, SumTree};
|
use sum_tree::{Bias, Dimensions, SumTree};
|
||||||
|
|
||||||
|
type RenderItemFn = dyn FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static;
|
||||||
|
|
||||||
/// Construct a new list element
|
/// Construct a new list element
|
||||||
pub fn list(state: ListState) -> List {
|
pub fn list(
|
||||||
|
state: ListState,
|
||||||
|
render_item: impl FnMut(usize, &mut Window, &mut App) -> AnyElement + 'static,
|
||||||
|
) -> List {
|
||||||
List {
|
List {
|
||||||
state,
|
state,
|
||||||
|
render_item: Box::new(render_item),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
sizing_behavior: ListSizingBehavior::default(),
|
sizing_behavior: ListSizingBehavior::default(),
|
||||||
}
|
}
|
||||||
|
@ -30,6 +36,7 @@ pub fn list(state: ListState) -> List {
|
||||||
/// A list element
|
/// A list element
|
||||||
pub struct List {
|
pub struct List {
|
||||||
state: ListState,
|
state: ListState,
|
||||||
|
render_item: Box<RenderItemFn>,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
sizing_behavior: ListSizingBehavior,
|
sizing_behavior: ListSizingBehavior,
|
||||||
}
|
}
|
||||||
|
@ -55,7 +62,6 @@ impl std::fmt::Debug for ListState {
|
||||||
struct StateInner {
|
struct StateInner {
|
||||||
last_layout_bounds: Option<Bounds<Pixels>>,
|
last_layout_bounds: Option<Bounds<Pixels>>,
|
||||||
last_padding: Option<Edges<Pixels>>,
|
last_padding: Option<Edges<Pixels>>,
|
||||||
render_item: Box<dyn FnMut(usize, &mut Window, &mut App) -> AnyElement>,
|
|
||||||
items: SumTree<ListItem>,
|
items: SumTree<ListItem>,
|
||||||
logical_scroll_top: Option<ListOffset>,
|
logical_scroll_top: Option<ListOffset>,
|
||||||
alignment: ListAlignment,
|
alignment: ListAlignment,
|
||||||
|
@ -186,19 +192,10 @@ impl ListState {
|
||||||
/// above and below the visible area. Elements within this area will
|
/// above and below the visible area. Elements within this area will
|
||||||
/// be measured even though they are not visible. This can help ensure
|
/// be measured even though they are not visible. This can help ensure
|
||||||
/// that the list doesn't flicker or pop in when scrolling.
|
/// that the list doesn't flicker or pop in when scrolling.
|
||||||
pub fn new<R>(
|
pub fn new(item_count: usize, alignment: ListAlignment, overdraw: Pixels) -> Self {
|
||||||
item_count: usize,
|
|
||||||
alignment: ListAlignment,
|
|
||||||
overdraw: Pixels,
|
|
||||||
render_item: R,
|
|
||||||
) -> Self
|
|
||||||
where
|
|
||||||
R: 'static + FnMut(usize, &mut Window, &mut App) -> AnyElement,
|
|
||||||
{
|
|
||||||
let this = Self(Rc::new(RefCell::new(StateInner {
|
let this = Self(Rc::new(RefCell::new(StateInner {
|
||||||
last_layout_bounds: None,
|
last_layout_bounds: None,
|
||||||
last_padding: None,
|
last_padding: None,
|
||||||
render_item: Box::new(render_item),
|
|
||||||
items: SumTree::default(),
|
items: SumTree::default(),
|
||||||
logical_scroll_top: None,
|
logical_scroll_top: None,
|
||||||
alignment,
|
alignment,
|
||||||
|
@ -532,6 +529,7 @@ impl StateInner {
|
||||||
available_width: Option<Pixels>,
|
available_width: Option<Pixels>,
|
||||||
available_height: Pixels,
|
available_height: Pixels,
|
||||||
padding: &Edges<Pixels>,
|
padding: &Edges<Pixels>,
|
||||||
|
render_item: &mut RenderItemFn,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> LayoutItemsResponse {
|
) -> LayoutItemsResponse {
|
||||||
|
@ -566,7 +564,7 @@ impl StateInner {
|
||||||
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
// If we're within the visible area or the height wasn't cached, render and measure the item's element
|
||||||
if visible_height < available_height || size.is_none() {
|
if visible_height < available_height || size.is_none() {
|
||||||
let item_index = scroll_top.item_ix + ix;
|
let item_index = scroll_top.item_ix + ix;
|
||||||
let mut element = (self.render_item)(item_index, window, cx);
|
let mut element = render_item(item_index, window, cx);
|
||||||
let element_size = element.layout_as_root(available_item_space, window, cx);
|
let element_size = element.layout_as_root(available_item_space, window, cx);
|
||||||
size = Some(element_size);
|
size = Some(element_size);
|
||||||
if visible_height < available_height {
|
if visible_height < available_height {
|
||||||
|
@ -601,7 +599,7 @@ impl StateInner {
|
||||||
cursor.prev();
|
cursor.prev();
|
||||||
if let Some(item) = cursor.item() {
|
if let Some(item) = cursor.item() {
|
||||||
let item_index = cursor.start().0;
|
let item_index = cursor.start().0;
|
||||||
let mut element = (self.render_item)(item_index, window, cx);
|
let mut element = render_item(item_index, window, cx);
|
||||||
let element_size = element.layout_as_root(available_item_space, window, cx);
|
let element_size = element.layout_as_root(available_item_space, window, cx);
|
||||||
let focus_handle = item.focus_handle();
|
let focus_handle = item.focus_handle();
|
||||||
rendered_height += element_size.height;
|
rendered_height += element_size.height;
|
||||||
|
@ -650,7 +648,7 @@ impl StateInner {
|
||||||
let size = if let ListItem::Measured { size, .. } = item {
|
let size = if let ListItem::Measured { size, .. } = item {
|
||||||
*size
|
*size
|
||||||
} else {
|
} else {
|
||||||
let mut element = (self.render_item)(cursor.start().0, window, cx);
|
let mut element = render_item(cursor.start().0, window, cx);
|
||||||
element.layout_as_root(available_item_space, window, cx)
|
element.layout_as_root(available_item_space, window, cx)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -683,7 +681,7 @@ impl StateInner {
|
||||||
while let Some(item) = cursor.item() {
|
while let Some(item) = cursor.item() {
|
||||||
if item.contains_focused(window, cx) {
|
if item.contains_focused(window, cx) {
|
||||||
let item_index = cursor.start().0;
|
let item_index = cursor.start().0;
|
||||||
let mut element = (self.render_item)(cursor.start().0, window, cx);
|
let mut element = render_item(cursor.start().0, window, cx);
|
||||||
let size = element.layout_as_root(available_item_space, window, cx);
|
let size = element.layout_as_root(available_item_space, window, cx);
|
||||||
item_layouts.push_back(ItemLayout {
|
item_layouts.push_back(ItemLayout {
|
||||||
index: item_index,
|
index: item_index,
|
||||||
|
@ -708,6 +706,7 @@ impl StateInner {
|
||||||
bounds: Bounds<Pixels>,
|
bounds: Bounds<Pixels>,
|
||||||
padding: Edges<Pixels>,
|
padding: Edges<Pixels>,
|
||||||
autoscroll: bool,
|
autoscroll: bool,
|
||||||
|
render_item: &mut RenderItemFn,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Result<LayoutItemsResponse, ListOffset> {
|
) -> Result<LayoutItemsResponse, ListOffset> {
|
||||||
|
@ -716,6 +715,7 @@ impl StateInner {
|
||||||
Some(bounds.size.width),
|
Some(bounds.size.width),
|
||||||
bounds.size.height,
|
bounds.size.height,
|
||||||
&padding,
|
&padding,
|
||||||
|
render_item,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
@ -753,8 +753,7 @@ impl StateInner {
|
||||||
let Some(item) = cursor.item() else { break };
|
let Some(item) = cursor.item() else { break };
|
||||||
|
|
||||||
let size = item.size().unwrap_or_else(|| {
|
let size = item.size().unwrap_or_else(|| {
|
||||||
let mut item =
|
let mut item = render_item(cursor.start().0, window, cx);
|
||||||
(self.render_item)(cursor.start().0, window, cx);
|
|
||||||
let item_available_size = size(
|
let item_available_size = size(
|
||||||
bounds.size.width.into(),
|
bounds.size.width.into(),
|
||||||
AvailableSpace::MinContent,
|
AvailableSpace::MinContent,
|
||||||
|
@ -876,8 +875,14 @@ impl Element for List {
|
||||||
window.rem_size(),
|
window.rem_size(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let layout_response =
|
let layout_response = state.layout_items(
|
||||||
state.layout_items(None, available_height, &padding, window, cx);
|
None,
|
||||||
|
available_height,
|
||||||
|
&padding,
|
||||||
|
&mut self.render_item,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let max_element_width = layout_response.max_item_width;
|
let max_element_width = layout_response.max_item_width;
|
||||||
|
|
||||||
let summary = state.items.summary();
|
let summary = state.items.summary();
|
||||||
|
@ -951,15 +956,16 @@ impl Element for List {
|
||||||
let padding = style
|
let padding = style
|
||||||
.padding
|
.padding
|
||||||
.to_pixels(bounds.size.into(), window.rem_size());
|
.to_pixels(bounds.size.into(), window.rem_size());
|
||||||
let layout = match state.prepaint_items(bounds, padding, true, window, cx) {
|
let layout =
|
||||||
Ok(layout) => layout,
|
match state.prepaint_items(bounds, padding, true, &mut self.render_item, window, cx) {
|
||||||
Err(autoscroll_request) => {
|
Ok(layout) => layout,
|
||||||
state.logical_scroll_top = Some(autoscroll_request);
|
Err(autoscroll_request) => {
|
||||||
state
|
state.logical_scroll_top = Some(autoscroll_request);
|
||||||
.prepaint_items(bounds, padding, false, window, cx)
|
state
|
||||||
.unwrap()
|
.prepaint_items(bounds, padding, false, &mut self.render_item, window, cx)
|
||||||
}
|
.unwrap()
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
state.last_layout_bounds = Some(bounds);
|
state.last_layout_bounds = Some(bounds);
|
||||||
state.last_padding = Some(padding);
|
state.last_padding = Some(padding);
|
||||||
|
@ -1108,9 +1114,7 @@ mod test {
|
||||||
|
|
||||||
let cx = cx.add_empty_window();
|
let cx = cx.add_empty_window();
|
||||||
|
|
||||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _, _| {
|
let state = ListState::new(5, crate::ListAlignment::Top, px(10.));
|
||||||
div().h(px(10.)).w_full().into_any()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure that the list is scrolled to the top
|
// Ensure that the list is scrolled to the top
|
||||||
state.scroll_to(gpui::ListOffset {
|
state.scroll_to(gpui::ListOffset {
|
||||||
|
@ -1121,7 +1125,11 @@ mod test {
|
||||||
struct TestView(ListState);
|
struct TestView(ListState);
|
||||||
impl Render for TestView {
|
impl Render for TestView {
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
list(self.0.clone()).w_full().h_full()
|
list(self.0.clone(), |_, _, _| {
|
||||||
|
div().h(px(10.)).w_full().into_any()
|
||||||
|
})
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1154,14 +1162,16 @@ mod test {
|
||||||
|
|
||||||
let cx = cx.add_empty_window();
|
let cx = cx.add_empty_window();
|
||||||
|
|
||||||
let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _, _| {
|
let state = ListState::new(5, crate::ListAlignment::Top, px(10.));
|
||||||
div().h(px(20.)).w_full().into_any()
|
|
||||||
});
|
|
||||||
|
|
||||||
struct TestView(ListState);
|
struct TestView(ListState);
|
||||||
impl Render for TestView {
|
impl Render for TestView {
|
||||||
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
list(self.0.clone()).w_full().h_full()
|
list(self.0.clone(), |_, _, _| {
|
||||||
|
div().h(px(20.)).w_full().into_any()
|
||||||
|
})
|
||||||
|
.w_full()
|
||||||
|
.h_full()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ use workspace::item::{Item, ItemHandle};
|
||||||
use workspace::{Pane, Workspace};
|
use workspace::{Pane, Workspace};
|
||||||
|
|
||||||
use crate::markdown_elements::ParsedMarkdownElement;
|
use crate::markdown_elements::ParsedMarkdownElement;
|
||||||
|
use crate::markdown_renderer::CheckboxClickedEvent;
|
||||||
use crate::{
|
use crate::{
|
||||||
MovePageDown, MovePageUp, OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide,
|
MovePageDown, MovePageUp, OpenFollowingPreview, OpenPreview, OpenPreviewToTheSide,
|
||||||
markdown_elements::ParsedMarkdown,
|
markdown_elements::ParsedMarkdown,
|
||||||
|
@ -203,114 +204,7 @@ impl MarkdownPreviewView {
|
||||||
cx: &mut Context<Workspace>,
|
cx: &mut Context<Workspace>,
|
||||||
) -> Entity<Self> {
|
) -> Entity<Self> {
|
||||||
cx.new(|cx| {
|
cx.new(|cx| {
|
||||||
let view = cx.entity().downgrade();
|
let list_state = ListState::new(0, gpui::ListAlignment::Top, px(1000.));
|
||||||
|
|
||||||
let list_state = ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
if let Some(view) = view.upgrade() {
|
|
||||||
view.update(cx, |this: &mut Self, cx| {
|
|
||||||
let Some(contents) = &this.contents else {
|
|
||||||
return div().into_any();
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut render_cx =
|
|
||||||
RenderContext::new(Some(this.workspace.clone()), window, cx)
|
|
||||||
.with_checkbox_clicked_callback({
|
|
||||||
let view = view.clone();
|
|
||||||
move |checked, source_range, window, cx| {
|
|
||||||
view.update(cx, |view, cx| {
|
|
||||||
if let Some(editor) = view
|
|
||||||
.active_editor
|
|
||||||
.as_ref()
|
|
||||||
.map(|s| s.editor.clone())
|
|
||||||
{
|
|
||||||
editor.update(cx, |editor, cx| {
|
|
||||||
let task_marker =
|
|
||||||
if checked { "[x]" } else { "[ ]" };
|
|
||||||
|
|
||||||
editor.edit(
|
|
||||||
vec![(source_range, task_marker)],
|
|
||||||
cx,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
view.parse_markdown_from_active_editor(
|
|
||||||
false, window, cx,
|
|
||||||
);
|
|
||||||
cx.notify();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let block = contents.children.get(ix).unwrap();
|
|
||||||
let rendered_block = render_markdown_block(block, &mut render_cx);
|
|
||||||
|
|
||||||
let should_apply_padding = Self::should_apply_padding_between(
|
|
||||||
block,
|
|
||||||
contents.children.get(ix + 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id(ix)
|
|
||||||
.when(should_apply_padding, |this| {
|
|
||||||
this.pb(render_cx.scaled_rems(0.75))
|
|
||||||
})
|
|
||||||
.group("markdown-block")
|
|
||||||
.on_click(cx.listener(
|
|
||||||
move |this, event: &ClickEvent, window, cx| {
|
|
||||||
if event.click_count() == 2 {
|
|
||||||
if let Some(source_range) = this
|
|
||||||
.contents
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|c| c.children.get(ix))
|
|
||||||
.and_then(|block| block.source_range())
|
|
||||||
{
|
|
||||||
this.move_cursor_to_block(
|
|
||||||
window,
|
|
||||||
cx,
|
|
||||||
source_range.start..source_range.start,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.map(move |container| {
|
|
||||||
let indicator = div()
|
|
||||||
.h_full()
|
|
||||||
.w(px(4.0))
|
|
||||||
.when(ix == this.selected_block, |this| {
|
|
||||||
this.bg(cx.theme().colors().border)
|
|
||||||
})
|
|
||||||
.group_hover("markdown-block", |s| {
|
|
||||||
if ix == this.selected_block {
|
|
||||||
s
|
|
||||||
} else {
|
|
||||||
s.bg(cx.theme().colors().border_variant)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.rounded_xs();
|
|
||||||
|
|
||||||
container.child(
|
|
||||||
div()
|
|
||||||
.relative()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.pl(render_cx.scaled_rems(1.0))
|
|
||||||
.child(rendered_block),
|
|
||||||
)
|
|
||||||
.child(indicator.absolute().left_0().top_0()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
div().into_any()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut this = Self {
|
let mut this = Self {
|
||||||
selected_block: 0,
|
selected_block: 0,
|
||||||
|
@ -607,10 +501,107 @@ impl Render for MarkdownPreviewView {
|
||||||
.p_4()
|
.p_4()
|
||||||
.text_size(buffer_size)
|
.text_size(buffer_size)
|
||||||
.line_height(relative(buffer_line_height.value()))
|
.line_height(relative(buffer_line_height.value()))
|
||||||
.child(
|
.child(div().flex_grow().map(|this| {
|
||||||
div()
|
this.child(
|
||||||
.flex_grow()
|
list(
|
||||||
.map(|this| this.child(list(self.list_state.clone()).size_full())),
|
self.list_state.clone(),
|
||||||
)
|
cx.processor(|this, ix, window, cx| {
|
||||||
|
let Some(contents) = &this.contents else {
|
||||||
|
return div().into_any();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut render_cx =
|
||||||
|
RenderContext::new(Some(this.workspace.clone()), window, cx)
|
||||||
|
.with_checkbox_clicked_callback(cx.listener(
|
||||||
|
move |this, e: &CheckboxClickedEvent, window, cx| {
|
||||||
|
if let Some(editor) = this
|
||||||
|
.active_editor
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.editor.clone())
|
||||||
|
{
|
||||||
|
editor.update(cx, |editor, cx| {
|
||||||
|
let task_marker =
|
||||||
|
if e.checked() { "[x]" } else { "[ ]" };
|
||||||
|
|
||||||
|
editor.edit(
|
||||||
|
vec![(e.source_range(), task_marker)],
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
this.parse_markdown_from_active_editor(
|
||||||
|
false, window, cx,
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
let block = contents.children.get(ix).unwrap();
|
||||||
|
let rendered_block = render_markdown_block(block, &mut render_cx);
|
||||||
|
|
||||||
|
let should_apply_padding = Self::should_apply_padding_between(
|
||||||
|
block,
|
||||||
|
contents.children.get(ix + 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(ix)
|
||||||
|
.when(should_apply_padding, |this| {
|
||||||
|
this.pb(render_cx.scaled_rems(0.75))
|
||||||
|
})
|
||||||
|
.group("markdown-block")
|
||||||
|
.on_click(cx.listener(
|
||||||
|
move |this, event: &ClickEvent, window, cx| {
|
||||||
|
if event.click_count() == 2 {
|
||||||
|
if let Some(source_range) = this
|
||||||
|
.contents
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.children.get(ix))
|
||||||
|
.and_then(|block: &ParsedMarkdownElement| {
|
||||||
|
block.source_range()
|
||||||
|
})
|
||||||
|
{
|
||||||
|
this.move_cursor_to_block(
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
source_range.start..source_range.start,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.map(move |container| {
|
||||||
|
let indicator = div()
|
||||||
|
.h_full()
|
||||||
|
.w(px(4.0))
|
||||||
|
.when(ix == this.selected_block, |this| {
|
||||||
|
this.bg(cx.theme().colors().border)
|
||||||
|
})
|
||||||
|
.group_hover("markdown-block", |s| {
|
||||||
|
if ix == this.selected_block {
|
||||||
|
s
|
||||||
|
} else {
|
||||||
|
s.bg(cx.theme().colors().border_variant)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.rounded_xs();
|
||||||
|
|
||||||
|
container.child(
|
||||||
|
div()
|
||||||
|
.relative()
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.pl(render_cx.scaled_rems(1.0))
|
||||||
|
.child(rendered_block),
|
||||||
|
)
|
||||||
|
.child(indicator.absolute().left_0().top_0()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.into_any()
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,22 @@ use ui::{
|
||||||
};
|
};
|
||||||
use workspace::{OpenOptions, OpenVisible, Workspace};
|
use workspace::{OpenOptions, OpenVisible, Workspace};
|
||||||
|
|
||||||
type CheckboxClickedCallback = Arc<Box<dyn Fn(bool, Range<usize>, &mut Window, &mut App)>>;
|
pub struct CheckboxClickedEvent {
|
||||||
|
pub checked: bool,
|
||||||
|
pub source_range: Range<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CheckboxClickedEvent {
|
||||||
|
pub fn source_range(&self) -> Range<usize> {
|
||||||
|
self.source_range.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked(&self) -> bool {
|
||||||
|
self.checked
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckboxClickedCallback = Arc<Box<dyn Fn(&CheckboxClickedEvent, &mut Window, &mut App)>>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct RenderContext {
|
pub struct RenderContext {
|
||||||
|
@ -80,7 +95,7 @@ impl RenderContext {
|
||||||
|
|
||||||
pub fn with_checkbox_clicked_callback(
|
pub fn with_checkbox_clicked_callback(
|
||||||
mut self,
|
mut self,
|
||||||
callback: impl Fn(bool, Range<usize>, &mut Window, &mut App) + 'static,
|
callback: impl Fn(&CheckboxClickedEvent, &mut Window, &mut App) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.checkbox_clicked_callback = Some(Arc::new(Box::new(callback)));
|
self.checkbox_clicked_callback = Some(Arc::new(Box::new(callback)));
|
||||||
self
|
self
|
||||||
|
@ -229,7 +244,14 @@ fn render_markdown_list_item(
|
||||||
};
|
};
|
||||||
|
|
||||||
if window.modifiers().secondary() {
|
if window.modifiers().secondary() {
|
||||||
callback(checked, range.clone(), window, cx);
|
callback(
|
||||||
|
&CheckboxClickedEvent {
|
||||||
|
checked,
|
||||||
|
source_range: range.clone(),
|
||||||
|
},
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -292,7 +292,7 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let element_container = Self::create_element_container(container, cx);
|
let element_container = Self::create_element_container(container);
|
||||||
let scrollbar_state = match &element_container {
|
let scrollbar_state = match &element_container {
|
||||||
ElementContainer::UniformList(scroll_handle) => {
|
ElementContainer::UniformList(scroll_handle) => {
|
||||||
ScrollbarState::new(scroll_handle.clone())
|
ScrollbarState::new(scroll_handle.clone())
|
||||||
|
@ -323,31 +323,13 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_element_container(
|
fn create_element_container(container: ContainerKind) -> ElementContainer {
|
||||||
container: ContainerKind,
|
|
||||||
cx: &mut Context<Self>,
|
|
||||||
) -> ElementContainer {
|
|
||||||
match container {
|
match container {
|
||||||
ContainerKind::UniformList => {
|
ContainerKind::UniformList => {
|
||||||
ElementContainer::UniformList(UniformListScrollHandle::new())
|
ElementContainer::UniformList(UniformListScrollHandle::new())
|
||||||
}
|
}
|
||||||
ContainerKind::List => {
|
ContainerKind::List => {
|
||||||
let entity = cx.entity().downgrade();
|
ElementContainer::List(ListState::new(0, gpui::ListAlignment::Top, px(1000.)))
|
||||||
ElementContainer::List(ListState::new(
|
|
||||||
0,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
entity
|
|
||||||
.upgrade()
|
|
||||||
.map(|entity| {
|
|
||||||
entity.update(cx, |this, cx| {
|
|
||||||
this.render_element(window, cx, ix).into_any_element()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| div().into_any_element())
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -786,11 +768,16 @@ impl<D: PickerDelegate> Picker<D> {
|
||||||
.py_1()
|
.py_1()
|
||||||
.track_scroll(scroll_handle.clone())
|
.track_scroll(scroll_handle.clone())
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
ElementContainer::List(state) => list(state.clone())
|
ElementContainer::List(state) => list(
|
||||||
.with_sizing_behavior(sizing_behavior)
|
state.clone(),
|
||||||
.flex_grow()
|
cx.processor(|this, ix, window, cx| {
|
||||||
.py_2()
|
this.render_element(window, cx, ix).into_any_element()
|
||||||
.into_any_element(),
|
}),
|
||||||
|
)
|
||||||
|
.with_sizing_behavior(sizing_behavior)
|
||||||
|
.flex_grow()
|
||||||
|
.py_2()
|
||||||
|
.into_any_element(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -126,29 +126,7 @@ impl NotebookEditor {
|
||||||
let cell_count = cell_order.len();
|
let cell_count = cell_order.len();
|
||||||
|
|
||||||
let this = cx.entity();
|
let this = cx.entity();
|
||||||
let cell_list = ListState::new(
|
let cell_list = ListState::new(cell_count, gpui::ListAlignment::Top, px(1000.));
|
||||||
cell_count,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1000.),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
notebook_handle
|
|
||||||
.upgrade()
|
|
||||||
.and_then(|notebook_handle| {
|
|
||||||
notebook_handle.update(cx, |notebook, cx| {
|
|
||||||
notebook
|
|
||||||
.cell_order
|
|
||||||
.get(ix)
|
|
||||||
.and_then(|cell_id| notebook.cell_map.get(cell_id))
|
|
||||||
.map(|cell| {
|
|
||||||
notebook
|
|
||||||
.render_cell(ix, cell, window, cx)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| div().into_any())
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
project,
|
project,
|
||||||
|
@ -544,7 +522,19 @@ impl Render for NotebookEditor {
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.size_full()
|
.size_full()
|
||||||
.overflow_y_scroll()
|
.overflow_y_scroll()
|
||||||
.child(list(self.cell_list.clone()).size_full()),
|
.child(list(
|
||||||
|
self.cell_list.clone(),
|
||||||
|
cx.processor(|this, ix, window, cx| {
|
||||||
|
this.cell_order
|
||||||
|
.get(ix)
|
||||||
|
.and_then(|cell_id| this.cell_map.get(cell_id))
|
||||||
|
.map(|cell| {
|
||||||
|
this.render_cell(ix, cell, window, cx).into_any_element()
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| div().into_any())
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.size_full(),
|
||||||
)
|
)
|
||||||
.child(self.render_notebook_controls(window, cx))
|
.child(self.render_notebook_controls(window, cx))
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,21 +115,9 @@ impl ProjectIndexDebugView {
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
let view = cx.entity().downgrade();
|
|
||||||
this.selected_path = Some(PathState {
|
this.selected_path = Some(PathState {
|
||||||
path: file_path,
|
path: file_path,
|
||||||
list_state: ListState::new(
|
list_state: ListState::new(chunks.len(), gpui::ListAlignment::Top, px(100.)),
|
||||||
chunks.len(),
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(100.),
|
|
||||||
move |ix, _, cx| {
|
|
||||||
if let Some(view) = view.upgrade() {
|
|
||||||
view.update(cx, |view, cx| view.render_chunk(ix, cx))
|
|
||||||
} else {
|
|
||||||
div().into_any()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
chunks,
|
chunks,
|
||||||
});
|
});
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
@ -219,7 +207,13 @@ impl Render for ProjectIndexDebugView {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(list(selected_path.list_state.clone()).size_full())
|
.child(
|
||||||
|
list(
|
||||||
|
selected_path.list_state.clone(),
|
||||||
|
cx.processor(|this, ix, _, cx| this.render_chunk(ix, cx)),
|
||||||
|
)
|
||||||
|
.size_full(),
|
||||||
|
)
|
||||||
.size_full()
|
.size_full()
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -107,6 +107,7 @@ struct ComponentPreview {
|
||||||
active_thread: Option<Entity<ActiveThread>>,
|
active_thread: Option<Entity<ActiveThread>>,
|
||||||
reset_key: usize,
|
reset_key: usize,
|
||||||
component_list: ListState,
|
component_list: ListState,
|
||||||
|
entries: Vec<PreviewEntry>,
|
||||||
component_map: HashMap<ComponentId, ComponentMetadata>,
|
component_map: HashMap<ComponentId, ComponentMetadata>,
|
||||||
components: Vec<ComponentMetadata>,
|
components: Vec<ComponentMetadata>,
|
||||||
cursor_index: usize,
|
cursor_index: usize,
|
||||||
|
@ -172,17 +173,6 @@ impl ComponentPreview {
|
||||||
sorted_components.len(),
|
sorted_components.len(),
|
||||||
gpui::ListAlignment::Top,
|
gpui::ListAlignment::Top,
|
||||||
px(1500.0),
|
px(1500.0),
|
||||||
{
|
|
||||||
let this = cx.entity().downgrade();
|
|
||||||
move |ix, window: &mut Window, cx: &mut App| {
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
let component = this.get_component(ix);
|
|
||||||
this.render_preview(&component, window, cx)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut component_preview = Self {
|
let mut component_preview = Self {
|
||||||
|
@ -190,6 +180,7 @@ impl ComponentPreview {
|
||||||
active_thread: None,
|
active_thread: None,
|
||||||
reset_key: 0,
|
reset_key: 0,
|
||||||
component_list,
|
component_list,
|
||||||
|
entries: Vec::new(),
|
||||||
component_map: component_registry.component_map(),
|
component_map: component_registry.component_map(),
|
||||||
components: sorted_components,
|
components: sorted_components,
|
||||||
cursor_index: selected_index,
|
cursor_index: selected_index,
|
||||||
|
@ -276,10 +267,6 @@ impl ComponentPreview {
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_component(&self, ix: usize) -> ComponentMetadata {
|
|
||||||
self.components[ix].clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn filtered_components(&self) -> Vec<ComponentMetadata> {
|
fn filtered_components(&self) -> Vec<ComponentMetadata> {
|
||||||
if self.filter_text.is_empty() {
|
if self.filter_text.is_empty() {
|
||||||
return self.components.clone();
|
return self.components.clone();
|
||||||
|
@ -420,7 +407,6 @@ impl ComponentPreview {
|
||||||
fn update_component_list(&mut self, cx: &mut Context<Self>) {
|
fn update_component_list(&mut self, cx: &mut Context<Self>) {
|
||||||
let entries = self.scope_ordered_entries();
|
let entries = self.scope_ordered_entries();
|
||||||
let new_len = entries.len();
|
let new_len = entries.len();
|
||||||
let weak_entity = cx.entity().downgrade();
|
|
||||||
|
|
||||||
if new_len > 0 {
|
if new_len > 0 {
|
||||||
self.nav_scroll_handle
|
self.nav_scroll_handle
|
||||||
|
@ -446,56 +432,9 @@ impl ComponentPreview {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.component_list = ListState::new(
|
self.component_list = ListState::new(new_len, gpui::ListAlignment::Top, px(1500.0));
|
||||||
filtered_components.len(),
|
self.entries = entries;
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1500.0),
|
|
||||||
{
|
|
||||||
let components = filtered_components.clone();
|
|
||||||
let this = cx.entity().downgrade();
|
|
||||||
move |ix, window: &mut Window, cx: &mut App| {
|
|
||||||
if ix >= components.len() {
|
|
||||||
return div().w_full().h_0().into_any_element();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.update(cx, |this, cx| {
|
|
||||||
let component = &components[ix];
|
|
||||||
this.render_preview(component, window, cx)
|
|
||||||
.into_any_element()
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let new_list = ListState::new(
|
|
||||||
new_len,
|
|
||||||
gpui::ListAlignment::Top,
|
|
||||||
px(1500.0),
|
|
||||||
move |ix, window, cx| {
|
|
||||||
if ix >= entries.len() {
|
|
||||||
return div().w_full().h_0().into_any_element();
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry = &entries[ix];
|
|
||||||
|
|
||||||
weak_entity
|
|
||||||
.update(cx, |this, cx| match entry {
|
|
||||||
PreviewEntry::Component(component, _) => this
|
|
||||||
.render_preview(component, window, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
PreviewEntry::SectionHeader(shared_string) => this
|
|
||||||
.render_scope_header(ix, shared_string.clone(), window, cx)
|
|
||||||
.into_any_element(),
|
|
||||||
PreviewEntry::AllComponents => div().w_full().h_0().into_any_element(),
|
|
||||||
PreviewEntry::ActiveThread => div().w_full().h_0().into_any_element(),
|
|
||||||
PreviewEntry::Separator => div().w_full().h_0().into_any_element(),
|
|
||||||
})
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
self.component_list = new_list;
|
|
||||||
cx.emit(ItemEvent::UpdateTab);
|
cx.emit(ItemEvent::UpdateTab);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,10 +611,35 @@ impl ComponentPreview {
|
||||||
.child(format!("No components matching '{}'.", self.filter_text))
|
.child(format!("No components matching '{}'.", self.filter_text))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
} else {
|
} else {
|
||||||
list(self.component_list.clone())
|
list(
|
||||||
.flex_grow()
|
self.component_list.clone(),
|
||||||
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
cx.processor(|this, ix, window, cx| {
|
||||||
.into_any_element()
|
if ix >= this.entries.len() {
|
||||||
|
return div().w_full().h_0().into_any_element();
|
||||||
|
}
|
||||||
|
|
||||||
|
let entry = &this.entries[ix];
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
PreviewEntry::Component(component, _) => this
|
||||||
|
.render_preview(component, window, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
PreviewEntry::SectionHeader(shared_string) => this
|
||||||
|
.render_scope_header(ix, shared_string.clone(), window, cx)
|
||||||
|
.into_any_element(),
|
||||||
|
PreviewEntry::AllComponents => {
|
||||||
|
div().w_full().h_0().into_any_element()
|
||||||
|
}
|
||||||
|
PreviewEntry::ActiveThread => {
|
||||||
|
div().w_full().h_0().into_any_element()
|
||||||
|
}
|
||||||
|
PreviewEntry::Separator => div().w_full().h_0().into_any_element(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.flex_grow()
|
||||||
|
.with_sizing_behavior(gpui::ListSizingBehavior::Auto)
|
||||||
|
.into_any_element()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue