1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
//! `ScrollArea` — a bounded, scrollable container.
//!
//! Desktop UIs scroll; most builders assume their content fits. Wrap an
//! overflowing column (or row) in a `ScrollArea` and give it a bound. There are
//! two, and which one is right is a layout question, not a preference:
//! `max_height` for a list that occupies a fixed slice of a larger layout, and
//! `fill` for a pane that should be as tall as whatever the window gives it.
//! Each instance needs a unique id so gpui can track its scroll offset.
use crate::devtools::Probed;
use gpui::prelude::*;
use gpui::{div, px, AnyElement, App, ElementId, IntoElement, SharedString, Window};
/// A scrollable region. `ScrollArea::new("id").max_height(240.0)`, or
/// `ScrollArea::new("id").fill()` to take the space the parent has left.
#[derive(IntoElement)]
pub struct ScrollArea {
id: ElementId,
children: Vec<AnyElement>,
max_height: Option<f32>,
fill: bool,
horizontal: bool,
}
impl ScrollArea {
pub fn new(id: impl Into<ElementId>) -> Self {
ScrollArea {
id: id.into(),
children: Vec::new(),
max_height: None,
fill: false,
horizontal: false,
}
}
/// Clip to this height (px) and scroll past it.
pub fn max_height(mut self, height: f32) -> Self {
self.max_height = Some(height);
self
}
/// Take the space the parent has left over, and scroll past it — the mode
/// for a full-height pane, where any fixed number is wrong at every window
/// size but one.
///
/// The parent still has to be bounded itself; filling an unbounded parent
/// sizes to the content and there is nothing to scroll.
pub fn fill(mut self) -> Self {
self.fill = true;
self
}
/// Scroll horizontally instead of vertically.
pub fn horizontal(mut self, horizontal: bool) -> Self {
self.horizontal = horizontal;
self
}
}
impl ParentElement for ScrollArea {
fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
self.children.extend(elements);
}
}
impl RenderOnce for ScrollArea {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let bound: SharedString = match (self.fill, self.max_height) {
(true, _) => "fill".into(),
(false, Some(height)) => format!("{height}px").into(),
(false, None) => "none".into(),
};
let mut el = div().id(self.id).flex();
el = if self.horizontal {
let el = el.flex_row().overflow_x_scroll();
if self.fill {
// Three settings for three parents: `flex_1` claims the leftover
// main axis under a flex parent, the relative size does the same
// under a plain block one (where grow means nothing, and where a
// flex basis would win anyway if both applied), and the zero
// minimum is what lets the box shrink under its content instead
// of pushing the parent open.
el.flex_1().w_full().min_w_0()
} else {
el
}
} else {
let el = el.flex_col().overflow_y_scroll();
if self.fill {
el.flex_1().h_full().min_h_0()
} else {
el
}
};
// A cap still applies while filling: grow into the window, but never
// past this.
if let Some(height) = self.max_height {
el = el.max_h(px(height));
}
el.children(self.children)
.probe("ScrollArea")
.attr("axis", if self.horizontal { "x" } else { "y" })
.attr("bound", bound)
}
}