Improve local tool execution and shell output panes

This commit is contained in:
2026-08-28 10:20:16 -05:00
parent a69b927cc3
commit 88c1ef9716
23 changed files with 807 additions and 238 deletions
@@ -38,6 +38,8 @@ pub struct ScrollTarget {
#[derive(Clone, Default)]
pub struct ClippedScrollData {
scroll_start_px: Pixels,
max_scroll_start_px: Pixels,
follow_end: bool,
pub(super) scroll_to_position: Option<ScrollTarget>,
selection_scroll_anchor: Option<ClippedSelectionScrollAnchor>,
}
@@ -77,6 +79,36 @@ impl ClippedScrollStateHandle {
self.clipped_scroll_data.lock().scroll_start_px
}
/// Returns whether the scrollable is currently at its maximum scroll position.
///
/// The maximum is captured during layout, so consumers can implement follow-output
/// behavior without estimating content or viewport sizes themselves.
pub fn is_scrolled_to_end(&self) -> bool {
let data = self.clipped_scroll_data.lock();
data.scroll_start_px.as_f32() + 0.5 >= data.max_scroll_start_px.as_f32()
}
/// Controls whether layout should keep the scroll position docked to the content's end.
pub fn set_follow_end(&self, follow_end: bool) {
self.clipped_scroll_data.lock().follow_end = follow_end;
}
pub(in crate::elements) fn update_scroll_extent(
&self,
visible_px: Pixels,
total_size: Pixels,
) -> Pixels {
let max_scroll_start_px = (total_size - visible_px).max(Pixels::zero());
let mut data = self.clipped_scroll_data.lock();
data.max_scroll_start_px = max_scroll_start_px;
data.scroll_start_px = if data.follow_end {
max_scroll_start_px
} else {
data.scroll_start_px.min(max_scroll_start_px)
};
data.scroll_start_px
}
pub fn scroll_by(&self, delta: Pixels) {
self.scroll_to(self.scroll_start() + delta);
}
@@ -417,12 +449,8 @@ impl Element for ClippedScrollable {
// Make sure that the new layout doesn't put the scroll bar in an invalid
// location.
if let Some(scroll_data) = self.scroll_data(app) {
let max_scroll_top =
(scroll_data.total_size - scroll_data.visible_px).max(Pixels::zero());
let scroll_top = scroll_data.scroll_start;
if scroll_top > max_scroll_top {
self.state.scroll_to(max_scroll_top);
}
self.state
.update_scroll_extent(scroll_data.visible_px, scroll_data.total_size);
}
}
}
@@ -1577,10 +1577,12 @@ impl SelectableElement for NewScrollable {
impl ClippedScrollStateHandle {
fn scroll_data(&self, viewport_size: Vector2F, child_size: Vector2F, axis: Axis) -> ScrollData {
let visible_px = viewport_size.along(axis).into_pixels();
let total_size = child_size.along(axis).into_pixels();
ScrollData {
scroll_start: self.scroll_start(),
visible_px: (viewport_size.along(axis)).into_pixels(),
total_size: child_size.along(axis).into_pixels(),
scroll_start: self.update_scroll_extent(visible_px, total_size),
visible_px,
total_size,
}
}
}