Skip to content
170 changes: 170 additions & 0 deletions crates/bevy_ui/src/ui_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,74 @@ impl ComputedNode {

clip_rect
}

const fn compute_thumb(
gutter_min: f32,
content_length: f32,
gutter_length: f32,
scroll_position: f32,
) -> [f32; 2] {
if content_length <= gutter_length {
return [gutter_min, gutter_min + gutter_length];
}
let thumb_len = gutter_length * gutter_length / content_length;
let thumb_min = gutter_min + scroll_position * gutter_length / content_length;
[thumb_min, thumb_min + thumb_len]
}

/// Compute the bounds of the horizontal scrollbar and the thumb
/// in object-centered coordinates.
pub fn horizontal_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
if self.scrollbar_size.y <= 0. {
return None;
}
let content_inset = self.content_inset();
let half_size = 0.5 * self.size;
let min_x = -half_size.x + content_inset.left;
let max_x = half_size.x - content_inset.right - self.scrollbar_size.x;
let max_y = half_size.y - content_inset.bottom;
let min_y = max_y - self.scrollbar_size.y;
let gutter = Rect {
min: Vec2::new(min_x, min_y),
max: Vec2::new(max_x, max_y),
};
Some((
gutter,
Self::compute_thumb(
gutter.min.x,
self.content_size.x,
gutter.size().x,
self.scroll_position.x,
),
))
}

/// Compute the bounds of the vertical scrollbar and the thumb
/// in object-centered coordinates.
pub fn vertical_scrollbar(&self) -> Option<(Rect, [f32; 2])> {
if self.scrollbar_size.x <= 0. {
return None;
}
let content_inset = self.content_inset();
let half_size = 0.5 * self.size;
let max_x = half_size.x - content_inset.right;
let min_x = max_x - self.scrollbar_size.x;
let min_y = -half_size.y + content_inset.top;
let max_y = half_size.y - content_inset.bottom - self.scrollbar_size.y;
let gutter = Rect {
min: Vec2::new(min_x, min_y),
max: Vec2::new(max_x, max_y),
};
Some((
gutter,
Self::compute_thumb(
gutter.min.y,
self.content_size.y,
gutter.size().y,
self.scroll_position.y,
),
))
}
}

impl ComputedNode {
Expand Down Expand Up @@ -2916,6 +2984,10 @@ impl ComputedUiRenderTargetInfo {

#[cfg(test)]
mod tests {
use bevy_math::Rect;
use bevy_math::Vec2;

use crate::ComputedNode;
use crate::GridPlacement;

#[test]
Expand Down Expand Up @@ -2943,4 +3015,102 @@ mod tests {
assert_eq!(GridPlacement::start_span(3, 5).get_end(), None);
assert_eq!(GridPlacement::end_span(-4, 12).get_start(), None);
}

#[test]
fn computed_node_both_scrollbars() {
let node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::splat(10.),
content_size: Vec2::splat(100.),
..Default::default()
};

let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(40., 50.)
}
);
assert_eq!(thumb, [-50., 31.]);

let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 40.)
}
);
assert_eq!(thumb, [-50., 31.]);
}

#[test]
fn computed_node_single_horizontal_scrollbar() {
let mut node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::new(0., 10.),
content_size: Vec2::new(200., 100.),
scroll_position: Vec2::new(0., 0.),
..Default::default()
};

assert_eq!(None, node.vertical_scrollbar());

let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [-50., 0.]);

node.scroll_position.x += 100.;
let (gutter, thumb) = node.horizontal_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(-50., 40.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [0., 50.]);
}

#[test]
fn computed_node_single_vertical_scrollbar() {
let mut node = ComputedNode {
size: Vec2::splat(100.),
scrollbar_size: Vec2::new(10., 0.),
content_size: Vec2::new(100., 200.),
scroll_position: Vec2::new(0., 0.),
..Default::default()
};

assert_eq!(None, node.horizontal_scrollbar());

let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [-50., 0.]);

node.scroll_position.y += 100.;
let (gutter, thumb) = node.vertical_scrollbar().unwrap();
assert_eq!(
gutter,
Rect {
min: Vec2::new(40., -50.),
max: Vec2::new(50., 50.)
}
);
assert_eq!(thumb, [0., 50.]);
}
}