1use gpui::{
23 AnyElement, App, Bounds, Element, GlobalElementId, InspectorElementId, IntoElement, LayoutId,
24 ParentElement, Pixels, RenderOnce, Styled, Window, div, prelude::FluentBuilder, px,
25};
26use gpui_kit_semantics::{NodeSpec, Role, Semantic};
27use gpui_kit_theme::ActiveTheme;
28
29use crate::foundation::Ident;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
33pub struct FadeEdges {
34 pub top: bool,
35 pub bottom: bool,
36 pub left: bool,
37 pub right: bool,
38}
39
40impl FadeEdges {
41 pub fn vertical() -> Self {
43 Self {
44 top: true,
45 bottom: true,
46 ..Self::default()
47 }
48 }
49
50 pub fn horizontal() -> Self {
52 Self {
53 left: true,
54 right: true,
55 ..Self::default()
56 }
57 }
58
59 pub fn any(self) -> bool {
62 self.top || self.bottom || self.left || self.right
63 }
64
65 pub fn names(self) -> Vec<&'static str> {
67 [
68 self.top.then_some("top"),
69 self.bottom.then_some("bottom"),
70 self.left.then_some("left"),
71 self.right.then_some("right"),
72 ]
73 .into_iter()
74 .flatten()
75 .collect()
76 }
77}
78
79#[derive(IntoElement)]
81pub struct ScrollFade {
82 ident: Ident,
83 edges: FadeEdges,
84 band: Option<f32>,
85 fit_height: bool,
88 child: Option<AnyElement>,
89}
90
91impl std::fmt::Debug for ScrollFade {
92 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
93 formatter
94 .debug_struct("ScrollFade")
95 .field("ident", &self.ident)
96 .field("edges", &self.edges)
97 .field("band", &self.band)
98 .field("has_child", &self.child.is_some())
99 .finish()
100 }
101}
102
103impl ScrollFade {
104 pub fn new(ident: impl Into<Ident>) -> Self {
107 Self {
108 ident: ident.into(),
109 edges: FadeEdges::default(),
110 band: None,
111 fit_height: false,
112 child: None,
113 }
114 }
115
116 pub fn edges(mut self, edges: FadeEdges) -> Self {
117 self.edges = edges;
118 self
119 }
120
121 pub fn top(mut self, fade: bool) -> Self {
122 self.edges.top = fade;
123 self
124 }
125
126 pub fn bottom(mut self, fade: bool) -> Self {
127 self.edges.bottom = fade;
128 self
129 }
130
131 pub fn left(mut self, fade: bool) -> Self {
132 self.edges.left = fade;
133 self
134 }
135
136 pub fn right(mut self, fade: bool) -> Self {
137 self.edges.right = fade;
138 self
139 }
140
141 pub fn band(mut self, band: f32) -> Self {
144 self.band = Some(band.max(0.0));
145 self
146 }
147
148 pub fn fit_height(mut self) -> Self {
155 self.fit_height = true;
156 self
157 }
158
159 pub fn child(mut self, child: impl IntoElement) -> Self {
160 self.child = Some(child.into_any_element());
161 self
162 }
163}
164
165impl RenderOnce for ScrollFade {
166 fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
167 let band = self.band.unwrap_or(cx.theme().effects.edge_fade_band);
168 let edges = self.edges;
169 let region = div()
172 .w_full()
173 .when(!self.fit_height, |element| element.h_full())
174 .children(self.child)
175 .semantic_in(cx, {
176 let spec = NodeSpec::new(self.ident.semantic_id(), Role::Region);
177 match edges.names().as_slice() {
178 [] => spec.value("none"),
179 names => spec.value(names.join(" ")),
180 }
181 })
182 .into_any_element();
183
184 Faded {
185 edges,
186 band: px(band),
187 child: region,
188 }
189 }
190}
191
192struct Faded {
194 edges: FadeEdges,
195 band: Pixels,
196 child: AnyElement,
197}
198
199impl Element for Faded {
200 type RequestLayoutState = ();
201 type PrepaintState = ();
202
203 fn id(&self) -> Option<gpui::ElementId> {
204 None
205 }
206
207 fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
208 None
209 }
210
211 fn request_layout(
212 &mut self,
213 _id: Option<&GlobalElementId>,
214 _inspector_id: Option<&InspectorElementId>,
215 window: &mut Window,
216 cx: &mut App,
217 ) -> (LayoutId, ()) {
218 (self.child.request_layout(window, cx), ())
219 }
220
221 fn prepaint(
222 &mut self,
223 _id: Option<&GlobalElementId>,
224 _inspector_id: Option<&InspectorElementId>,
225 _bounds: Bounds<Pixels>,
226 _request_layout: &mut Self::RequestLayoutState,
227 window: &mut Window,
228 cx: &mut App,
229 ) {
230 self.child.prepaint(window, cx);
231 }
232
233 fn paint(
234 &mut self,
235 _id: Option<&GlobalElementId>,
236 _inspector_id: Option<&InspectorElementId>,
237 bounds: Bounds<Pixels>,
238 _request_layout: &mut Self::RequestLayoutState,
239 _prepaint: &mut Self::PrepaintState,
240 window: &mut Window,
241 cx: &mut App,
242 ) {
243 let fade = (self.edges.any() && self.band > px(0.0)).then_some(gpui::EdgeFade {
244 bounds,
245 band: self.band,
246 top: self.edges.top,
247 bottom: self.edges.bottom,
248 left: self.edges.left,
249 right: self.edges.right,
250 });
251 window.with_edge_fade(fade, |window| self.child.paint(window, cx));
252 }
253}
254
255impl IntoElement for Faded {
256 type Element = Self;
257
258 fn into_element(self) -> Self::Element {
259 self
260 }
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266
267 #[test]
268 fn a_region_that_hides_nothing_fades_at_no_edge() {
269 assert!(!FadeEdges::default().any());
270 assert!(FadeEdges::vertical().any());
271 assert_eq!(FadeEdges::horizontal().names(), vec!["left", "right"]);
272 }
273}