1use fret_core::{Color, Px};
2use fret_ui::element::{
3 AnyElement, ContainerProps, InsetStyle, LayoutStyle, Length, Overflow, PositionStyle,
4 ScrollAxis, ScrollProps, ScrollbarAxis, ScrollbarProps, ScrollbarStyle, SizeStyle, StackProps,
5};
6use fret_ui::scroll::ScrollHandle;
7use fret_ui::{ElementContext, Theme, UiHost};
8
9use crate::IntoUiElement;
10use crate::LayoutRefinement;
11use crate::collect_children;
12use crate::declarative::style;
13
14#[track_caller]
19pub fn overflow_scroll<H: UiHost, I, T>(
20 cx: &mut ElementContext<'_, H>,
21 layout: LayoutRefinement,
22 show_scrollbar: bool,
23 f: impl FnOnce(&mut ElementContext<'_, H>) -> I,
24) -> AnyElement
25where
26 I: IntoIterator<Item = T>,
27 T: IntoUiElement<H>,
28{
29 let (layout, scrollbar_w, thumb, thumb_hover) = {
30 let theme = Theme::global(&*cx.app);
31 let layout = style::layout_style(theme, layout);
32
33 let scrollbar_w = theme.metric_token("metric.scrollbar.width");
34
35 let thumb = theme.color_token("scrollbar.thumb.background");
36 let thumb_hover = theme.color_token("scrollbar.thumb.hover.background");
37
38 (layout, scrollbar_w, thumb, thumb_hover)
39 };
40
41 cx.stack_props(StackProps { layout }, move |cx| {
42 let handle = cx.slot_state(ScrollHandle::default, |h| h.clone());
43 let mut scroll_layout = LayoutStyle::default();
44 scroll_layout.size.width = Length::Fill;
45 scroll_layout.size.height = Length::Fill;
46 scroll_layout.overflow = Overflow::Clip;
47
48 let scroll = cx.scroll(
49 ScrollProps {
50 layout: scroll_layout,
51 scroll_handle: Some(handle.clone()),
52 ..Default::default()
53 },
54 move |cx| {
55 let items = f(cx);
56 collect_children(cx, items)
57 },
58 );
59
60 let scroll_id = scroll.id;
61 let mut children = vec![scroll];
62 if show_scrollbar {
63 let scrollbar_layout = LayoutStyle {
64 position: PositionStyle::Absolute,
65 inset: InsetStyle {
66 top: Some(Px(0.0)).into(),
67 right: Some(Px(0.0)).into(),
68 bottom: Some(Px(0.0)).into(),
69 left: None.into(),
70 },
71 size: SizeStyle {
72 width: Length::Px(scrollbar_w),
73 ..Default::default()
74 },
75 ..Default::default()
76 };
77
78 children.push(cx.scrollbar(ScrollbarProps {
79 layout: scrollbar_layout,
80 axis: ScrollbarAxis::Vertical,
81 scroll_target: Some(scroll_id),
82 scroll_handle: handle,
83 style: ScrollbarStyle {
84 thumb,
85 thumb_hover,
86 ..Default::default()
87 },
88 }));
89 }
90
91 children
92 })
93}
94
95pub fn overflow_scroll_with_handle<H: UiHost, I, T>(
96 cx: &mut ElementContext<'_, H>,
97 layout: LayoutRefinement,
98 show_scrollbar: bool,
99 handle: ScrollHandle,
100 f: impl FnOnce(&mut ElementContext<'_, H>) -> I,
101) -> AnyElement
102where
103 I: IntoIterator<Item = T>,
104 T: IntoUiElement<H>,
105{
106 let (layout, scrollbar_w, thumb, thumb_hover) = {
107 let theme = Theme::global(&*cx.app);
108 let layout = style::layout_style(theme, layout);
109
110 let scrollbar_w = theme.metric_token("metric.scrollbar.width");
111
112 let thumb = theme.color_token("scrollbar.thumb.background");
113 let thumb_hover = theme.color_token("scrollbar.thumb.hover.background");
114
115 (layout, scrollbar_w, thumb, thumb_hover)
116 };
117
118 cx.stack_props(StackProps { layout }, move |cx| {
119 let mut scroll_layout = LayoutStyle::default();
120 scroll_layout.size.width = Length::Fill;
121 scroll_layout.size.height = Length::Fill;
122 scroll_layout.overflow = Overflow::Clip;
123
124 let scroll = cx.scroll(
125 ScrollProps {
126 layout: scroll_layout,
127 scroll_handle: Some(handle.clone()),
128 ..Default::default()
129 },
130 move |cx| {
131 let items = f(cx);
132 collect_children(cx, items)
133 },
134 );
135
136 let scroll_id = scroll.id;
137 let mut children = vec![scroll];
138 if show_scrollbar {
139 let scrollbar_layout = LayoutStyle {
140 position: PositionStyle::Absolute,
141 inset: InsetStyle {
142 top: Some(Px(0.0)).into(),
143 right: Some(Px(0.0)).into(),
144 bottom: Some(Px(0.0)).into(),
145 left: None.into(),
146 },
147 size: SizeStyle {
148 width: Length::Px(scrollbar_w),
149 ..Default::default()
150 },
151 ..Default::default()
152 };
153
154 children.push(cx.scrollbar(ScrollbarProps {
155 layout: scrollbar_layout,
156 axis: ScrollbarAxis::Vertical,
157 scroll_target: Some(scroll_id),
158 scroll_handle: handle,
159 style: ScrollbarStyle {
160 thumb,
161 thumb_hover,
162 ..Default::default()
163 },
164 }));
165 }
166
167 children
168 })
169}
170
171pub fn overflow_scroll_with_handle_xy<H: UiHost, I, T>(
172 cx: &mut ElementContext<'_, H>,
173 layout: LayoutRefinement,
174 show_scrollbar_x: bool,
175 show_scrollbar_y: bool,
176 handle: ScrollHandle,
177 f: impl FnOnce(&mut ElementContext<'_, H>) -> I,
178) -> AnyElement
179where
180 I: IntoIterator<Item = T>,
181 T: IntoUiElement<H>,
182{
183 let (layout, scrollbar_w, thumb, thumb_hover, corner_bg) = {
184 let theme = Theme::global(&*cx.app);
185 let layout = style::layout_style(theme, layout);
186
187 let scrollbar_w = theme.metric_token("metric.scrollbar.width");
188
189 let thumb = theme.color_token("scrollbar.thumb.background");
190 let thumb_hover = theme.color_token("scrollbar.thumb.hover.background");
191
192 let corner_bg = theme
193 .color_by_key("scrollbar.corner.background")
194 .or_else(|| theme.color_by_key("scrollbar.track.background"))
195 .unwrap_or(Color::TRANSPARENT);
196
197 (layout, scrollbar_w, thumb, thumb_hover, corner_bg)
198 };
199
200 cx.stack_props(StackProps { layout }, move |cx| {
201 let mut scroll_layout = LayoutStyle::default();
202 scroll_layout.size.width = Length::Fill;
203 scroll_layout.size.height = Length::Fill;
204 scroll_layout.overflow = Overflow::Clip;
205
206 let scroll = cx.scroll(
207 ScrollProps {
208 layout: scroll_layout,
209 axis: ScrollAxis::Both,
210 scroll_handle: Some(handle.clone()),
211 ..Default::default()
212 },
213 move |cx| {
214 let items = f(cx);
215 collect_children(cx, items)
216 },
217 );
218
219 let scroll_id = scroll.id;
220 let mut children = vec![scroll];
221
222 if show_scrollbar_y {
223 let scrollbar_layout = LayoutStyle {
224 position: PositionStyle::Absolute,
225 inset: InsetStyle {
226 top: Some(Px(0.0)).into(),
227 right: Some(Px(0.0)).into(),
228 bottom: Some(if show_scrollbar_x {
229 scrollbar_w
230 } else {
231 Px(0.0)
232 })
233 .into(),
234 left: None.into(),
235 },
236 size: SizeStyle {
237 width: Length::Px(scrollbar_w),
238 ..Default::default()
239 },
240 ..Default::default()
241 };
242
243 children.push(cx.scrollbar(ScrollbarProps {
244 layout: scrollbar_layout,
245 axis: ScrollbarAxis::Vertical,
246 scroll_target: Some(scroll_id),
247 scroll_handle: handle.clone(),
248 style: ScrollbarStyle {
249 thumb,
250 thumb_hover,
251 ..Default::default()
252 },
253 }));
254 }
255
256 if show_scrollbar_x {
257 let scrollbar_layout = LayoutStyle {
258 position: PositionStyle::Absolute,
259 inset: InsetStyle {
260 top: None.into(),
261 right: Some(if show_scrollbar_y {
262 scrollbar_w
263 } else {
264 Px(0.0)
265 })
266 .into(),
267 bottom: Some(Px(0.0)).into(),
268 left: Some(Px(0.0)).into(),
269 },
270 size: SizeStyle {
271 height: Length::Px(scrollbar_w),
272 ..Default::default()
273 },
274 ..Default::default()
275 };
276
277 children.push(cx.scrollbar(ScrollbarProps {
278 layout: scrollbar_layout,
279 axis: ScrollbarAxis::Horizontal,
280 scroll_target: Some(scroll_id),
281 scroll_handle: handle.clone(),
282 style: ScrollbarStyle {
283 thumb,
284 thumb_hover,
285 ..Default::default()
286 },
287 }));
288 }
289
290 if show_scrollbar_x && show_scrollbar_y {
291 let corner_layout = LayoutStyle {
292 position: PositionStyle::Absolute,
293 inset: InsetStyle {
294 right: Some(Px(0.0)).into(),
295 bottom: Some(Px(0.0)).into(),
296 ..Default::default()
297 },
298 size: SizeStyle {
299 width: Length::Px(scrollbar_w),
300 height: Length::Px(scrollbar_w),
301 ..Default::default()
302 },
303 ..Default::default()
304 };
305
306 children.push(cx.container(
307 ContainerProps {
308 layout: corner_layout,
309 background: Some(corner_bg),
310 ..Default::default()
311 },
312 |_cx| vec![],
313 ));
314 }
315
316 children
317 })
318}
319
320#[track_caller]
321pub fn overflow_scrollbar<H: UiHost, I, T>(
322 cx: &mut ElementContext<'_, H>,
323 layout: LayoutRefinement,
324 f: impl FnOnce(&mut ElementContext<'_, H>) -> I,
325) -> AnyElement
326where
327 I: IntoIterator<Item = T>,
328 T: IntoUiElement<H>,
329{
330 overflow_scroll(cx, layout, true, f)
331}
332
333#[track_caller]
338pub fn overflow_scroll_content<H: UiHost, T>(
339 cx: &mut ElementContext<'_, H>,
340 layout: LayoutRefinement,
341 show_scrollbar: bool,
342 content: impl FnOnce(&mut ElementContext<'_, H>) -> T,
343) -> AnyElement
344where
345 T: IntoUiElement<H>,
346{
347 overflow_scroll(cx, layout, show_scrollbar, |cx| {
348 std::iter::once(content(cx))
349 })
350}
351
352#[track_caller]
354pub fn overflow_scroll_x_content<H: UiHost, T>(
355 cx: &mut ElementContext<'_, H>,
356 layout: LayoutRefinement,
357 show_scrollbar_x: bool,
358 content: impl FnOnce(&mut ElementContext<'_, H>) -> T,
359) -> AnyElement
360where
361 T: IntoUiElement<H>,
362{
363 let (layout, scrollbar_w, thumb, thumb_hover) = {
364 let theme = Theme::global(&*cx.app);
365 let layout = style::layout_style(theme, layout);
366
367 let scrollbar_w = theme.metric_token("metric.scrollbar.width");
368
369 let thumb = theme.color_token("scrollbar.thumb.background");
370 let thumb_hover = theme.color_token("scrollbar.thumb.hover.background");
371
372 (layout, scrollbar_w, thumb, thumb_hover)
373 };
374
375 cx.stack_props(StackProps { layout }, move |cx| {
376 let handle = cx.slot_state(ScrollHandle::default, |h| h.clone());
377 let mut scroll_layout = LayoutStyle::default();
378 scroll_layout.size.width = Length::Fill;
379 scroll_layout.size.height = Length::Auto;
382 scroll_layout.overflow = Overflow::Clip;
383
384 let scroll = cx.scroll(
385 ScrollProps {
386 layout: scroll_layout,
387 axis: ScrollAxis::X,
388 scroll_handle: Some(handle.clone()),
389 ..Default::default()
390 },
391 move |cx| {
392 let child = content(cx);
393 vec![IntoUiElement::into_element(child, cx)]
394 },
395 );
396
397 let scroll_id = scroll.id;
398 let mut children = vec![scroll];
399
400 if show_scrollbar_x {
401 let scrollbar_layout = LayoutStyle {
402 position: PositionStyle::Absolute,
403 inset: InsetStyle {
404 top: None.into(),
405 right: Some(Px(0.0)).into(),
406 bottom: Some(Px(0.0)).into(),
407 left: Some(Px(0.0)).into(),
408 },
409 size: SizeStyle {
410 height: Length::Px(scrollbar_w),
411 ..Default::default()
412 },
413 ..Default::default()
414 };
415
416 children.push(cx.scrollbar(ScrollbarProps {
417 layout: scrollbar_layout,
418 axis: ScrollbarAxis::Horizontal,
419 scroll_target: Some(scroll_id),
420 scroll_handle: handle,
421 style: ScrollbarStyle {
422 thumb,
423 thumb_hover,
424 ..Default::default()
425 },
426 }));
427 }
428
429 children
430 })
431}
432
433pub fn overflow_scroll_with_handle_xy_content<H: UiHost, T>(
441 cx: &mut ElementContext<'_, H>,
442 layout: LayoutRefinement,
443 show_scrollbar_x: bool,
444 show_scrollbar_y: bool,
445 handle: ScrollHandle,
446 content: impl FnOnce(&mut ElementContext<'_, H>) -> T,
447) -> AnyElement
448where
449 T: IntoUiElement<H>,
450{
451 overflow_scroll_with_handle_xy(
452 cx,
453 layout,
454 show_scrollbar_x,
455 show_scrollbar_y,
456 handle,
457 |cx| std::iter::once(content(cx)),
458 )
459}