Skip to content

Commit 63af390

Browse files
ickshonpekfc35
andauthored
scrollbar helper functions (#21937)
# Objective Add helper functions to `ComputedNode` to compute the scrollbar areas of a UI node. ## Solution Add `horizontal_scrollbar` and `vertical_scrollbar` functions to `ComputedNode`. If a scrollbar is present, these return an option wrapping a tuple of the node-centered bounding `Rect` for the scrollbar, and the start and end points of the thumb. ## Testing Includes a couple of tests in the `uinode` module. --------- Co-authored-by: Kevin Chen <[email protected]>
1 parent ae48687 commit 63af390

File tree

1 file changed

+170
-0
lines changed

1 file changed

+170
-0
lines changed

crates/bevy_ui/src/ui_node.rs

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,74 @@ impl ComputedNode {
298298

299299
clip_rect
300300
}
301+
302+
const fn compute_thumb(
303+
gutter_min: f32,
304+
content_length: f32,
305+
gutter_length: f32,
306+
scroll_position: f32,
307+
) -> [f32; 2] {
308+
if content_length <= gutter_length {
309+
return [gutter_min, gutter_min + gutter_length];
310+
}
311+
let thumb_len = gutter_length * gutter_length / content_length;
312+
let thumb_min = gutter_min + scroll_position * gutter_length / content_length;
313+
[thumb_min, thumb_min + thumb_len]
314+
}
315+
316+
/// Compute the bounds of the horizontal scrollbar and the thumb
317+
/// in object-centered coordinates.
318+
pub fn horizontal_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
319+
if self.scrollbar_size.y <= 0. {
320+
return None;
321+
}
322+
let content_inset = self.content_inset();
323+
let half_size = 0.5 * self.size;
324+
let min_x = -half_size.x + content_inset.left;
325+
let max_x = half_size.x - content_inset.right - self.scrollbar_size.x;
326+
let max_y = half_size.y - content_inset.bottom;
327+
let min_y = max_y - self.scrollbar_size.y;
328+
let gutter = Rect {
329+
min: Vec2::new(min_x, min_y),
330+
max: Vec2::new(max_x, max_y),
331+
};
332+
Some((
333+
gutter,
334+
Self::compute_thumb(
335+
gutter.min.x,
336+
self.content_size.x,
337+
gutter.size().x,
338+
self.scroll_position.x,
339+
),
340+
))
341+
}
342+
343+
/// Compute the bounds of the vertical scrollbar and the thumb
344+
/// in object-centered coordinates.
345+
pub fn vertical_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
346+
if self.scrollbar_size.x <= 0. {
347+
return None;
348+
}
349+
let content_inset = self.content_inset();
350+
let half_size = 0.5 * self.size;
351+
let max_x = half_size.x - content_inset.right;
352+
let min_x = max_x - self.scrollbar_size.x;
353+
let min_y = -half_size.y + content_inset.top;
354+
let max_y = half_size.y - content_inset.bottom - self.scrollbar_size.y;
355+
let gutter = Rect {
356+
min: Vec2::new(min_x, min_y),
357+
max: Vec2::new(max_x, max_y),
358+
};
359+
Some((
360+
gutter,
361+
Self::compute_thumb(
362+
gutter.min.y,
363+
self.content_size.y,
364+
gutter.size().y,
365+
self.scroll_position.y,
366+
),
367+
))
368+
}
301369
}
302370

303371
impl ComputedNode {
@@ -2916,6 +2984,10 @@ impl ComputedUiRenderTargetInfo {
29162984

29172985
#[cfg(test)]
29182986
mod tests {
2987+
use bevy_math::Rect;
2988+
use bevy_math::Vec2;
2989+
2990+
use crate::ComputedNode;
29192991
use crate::GridPlacement;
29202992

29212993
#[test]
@@ -2943,4 +3015,102 @@ mod tests {
29433015
assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);
29443016
assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);
29453017
}
3018+
3019+
#[test]
3020+
fn computed_node_both_scrollbars() {
3021+
let node = ComputedNode {
3022+
size: Vec2::splat(100.),
3023+
scrollbar_size: Vec2::splat(10.),
3024+
content_size: Vec2::splat(100.),
3025+
..Default::default()
3026+
};
3027+
3028+
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3029+
assert_eq!(
3030+
gutter,
3031+
Rect {
3032+
min: Vec2::new(-50., 40.),
3033+
max: Vec2::new(40., 50.)
3034+
}
3035+
);
3036+
assert_eq!(thumb, [-50., 31.]);
3037+
3038+
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3039+
assert_eq!(
3040+
gutter,
3041+
Rect {
3042+
min: Vec2::new(40., -50.),
3043+
max: Vec2::new(50., 40.)
3044+
}
3045+
);
3046+
assert_eq!(thumb, [-50., 31.]);
3047+
}
3048+
3049+
#[test]
3050+
fn computed_node_single_horizontal_scrollbar() {
3051+
let mut node = ComputedNode {
3052+
size: Vec2::splat(100.),
3053+
scrollbar_size: Vec2::new(0., 10.),
3054+
content_size: Vec2::new(200., 100.),
3055+
scroll_position: Vec2::new(0., 0.),
3056+
..Default::default()
3057+
};
3058+
3059+
assert_eq!(None, node.vertical_scrollbar());
3060+
3061+
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3062+
assert_eq!(
3063+
gutter,
3064+
Rect {
3065+
min: Vec2::new(-50., 40.),
3066+
max: Vec2::new(50., 50.)
3067+
}
3068+
);
3069+
assert_eq!(thumb, [-50., 0.]);
3070+
3071+
node.scroll_position.x += 100.;
3072+
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
3073+
assert_eq!(
3074+
gutter,
3075+
Rect {
3076+
min: Vec2::new(-50., 40.),
3077+
max: Vec2::new(50., 50.)
3078+
}
3079+
);
3080+
assert_eq!(thumb, [0., 50.]);
3081+
}
3082+
3083+
#[test]
3084+
fn computed_node_single_vertical_scrollbar() {
3085+
let mut node = ComputedNode {
3086+
size: Vec2::splat(100.),
3087+
scrollbar_size: Vec2::new(10., 0.),
3088+
content_size: Vec2::new(100., 200.),
3089+
scroll_position: Vec2::new(0., 0.),
3090+
..Default::default()
3091+
};
3092+
3093+
assert_eq!(None, node.horizontal_scrollbar());
3094+
3095+
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3096+
assert_eq!(
3097+
gutter,
3098+
Rect {
3099+
min: Vec2::new(40., -50.),
3100+
max: Vec2::new(50., 50.)
3101+
}
3102+
);
3103+
assert_eq!(thumb, [-50., 0.]);
3104+
3105+
node.scroll_position.y += 100.;
3106+
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
3107+
assert_eq!(
3108+
gutter,
3109+
Rect {
3110+
min: Vec2::new(40., -50.),
3111+
max: Vec2::new(50., 50.)
3112+
}
3113+
);
3114+
assert_eq!(thumb, [0., 50.]);
3115+
}
29463116
}

0 commit comments

Comments
 (0)