@@ -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
303371impl ComputedNode {
@@ -2916,6 +2984,10 @@ impl ComputedUiRenderTargetInfo {
29162984
29172985#[ cfg( test) ]
29182986mod 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