docs/bevy/examples/ui/scroll_and_overflow/scrollbars.rs 6.8 K · 193 lines · raw · history

1 //! Demonstrations of scrolling and scrollbars.
2
3 use bevy::{
4 ecs::{relationship::RelatedSpawner, spawn::SpawnWith},
5 input_focus::tab_navigation::{TabGroup, TabNavigationPlugin},
6 picking::hover::Hovered,
7 prelude::*,
8 ui_widgets::{ControlOrientation, Scrollbar, ScrollbarDragState, ScrollbarThumb},
9 };
10
11 fn main() {
12 App::new()
13 .add_plugins((DefaultPlugins, TabNavigationPlugin))
14 .insert_resource(UiScale(1.25))
15 .add_systems(Startup, setup_view_root)
16 .add_systems(Update, update_scrollbar_thumb)
17 .run();
18 }
19
20 fn setup_view_root(mut commands: Commands) {
21 let camera = commands.spawn((Camera::default(), Camera2d)).id();
22
23 commands.spawn((
24 Node {
25 display: Display::Flex,
26 flex_direction: FlexDirection::Column,
27 position_type: PositionType::Absolute,
28 left: px(0),
29 top: px(0),
30 right: px(0),
31 bottom: px(0),
32 padding: UiRect::all(px(3)),
33 row_gap: px(6),
34 ..Default::default()
35 },
36 BackgroundColor(Color::srgb(0.1, 0.1, 0.1)),
37 UiTargetCamera(camera),
38 TabGroup::default(),
39 Children::spawn((Spawn(Text::new("Scrolling")), Spawn(scroll_area_demo()))),
40 ));
41 }
42
43 /// Create a scrolling area.
44 ///
45 /// The "scroll area" is a container that can be scrolled. It has a nested structure which is
46 /// three levels deep:
47 /// - The outermost node is a grid that contains the scroll area and the scrollbars.
48 /// - The scroll area is a flex container that contains the scrollable content. This
49 /// is the element that has the `overflow: scroll` property.
50 /// - The scrollable content consists of the elements actually displayed in the scrolling area.
51 fn scroll_area_demo() -> impl Bundle {
52 (
53 // Frame element which contains the scroll area and scrollbars.
54 Node {
55 display: Display::Grid,
56 width: px(200),
57 height: px(150),
58 grid_template_columns: vec![RepeatedGridTrack::flex(1, 1.), RepeatedGridTrack::auto(1)],
59 grid_template_rows: vec![RepeatedGridTrack::flex(1, 1.), RepeatedGridTrack::auto(1)],
60 row_gap: px(2),
61 column_gap: px(2),
62 ..default()
63 },
64 Children::spawn((SpawnWith(|parent: &mut RelatedSpawner<ChildOf>| {
65 // The actual scrolling area.
66 // Note that we're using `SpawnWith` here because we need to get the entity id of the
67 // scroll area in order to set the target of the scrollbars.
68 let scroll_area_id = parent
69 .spawn((
70 Node {
71 display: Display::Flex,
72 flex_direction: FlexDirection::Column,
73 padding: UiRect::all(px(4)),
74 overflow: Overflow::scroll(),
75 ..default()
76 },
77 BackgroundColor(colors::GRAY1.into()),
78 ScrollPosition(Vec2::new(0.0, 10.0)),
79 Children::spawn((
80 // The actual content of the scrolling area
81 Spawn(text_row("Alpha Wolf")),
82 Spawn(text_row("Beta Blocker")),
83 Spawn(text_row("Delta Sleep")),
84 Spawn(text_row("Gamma Ray")),
85 Spawn(text_row("Epsilon Eridani")),
86 Spawn(text_row("Zeta Function")),
87 Spawn(text_row("Lambda Calculus")),
88 Spawn(text_row("Nu Metal")),
89 Spawn(text_row("Pi Day")),
90 Spawn(text_row("Chi Pants")),
91 Spawn(text_row("Psi Powers")),
92 Spawn(text_row("Omega Fatty Acid")),
93 )),
94 ))
95 .id();
96
97 // Vertical scrollbar
98 parent.spawn((
99 Node {
100 min_width: px(8),
101 grid_row: GridPlacement::start(1),
102 grid_column: GridPlacement::start(2),
103 ..default()
104 },
105 Scrollbar {
106 orientation: ControlOrientation::Vertical,
107 target: scroll_area_id,
108 min_thumb_length: 8.0,
109 },
110 Children::spawn(Spawn((
111 Hovered::default(),
112 BackgroundColor(colors::GRAY2.into()),
113 BorderColor::all(colors::GRAY3),
114 ScrollbarThumb {
115 border_radius: BorderRadius::all(px(4)),
116 border: px(1).all(),
117 },
118 ))),
119 ));
120
121 // Horizontal scrollbar
122 parent.spawn((
123 Node {
124 min_height: px(8),
125 grid_row: GridPlacement::start(2),
126 grid_column: GridPlacement::start(1),
127 ..default()
128 },
129 Scrollbar {
130 orientation: ControlOrientation::Horizontal,
131 target: scroll_area_id,
132 min_thumb_length: 8.0,
133 },
134 Children::spawn(Spawn((
135 Hovered::default(),
136 BackgroundColor(colors::GRAY2.into()),
137 BorderColor::all(colors::GRAY3),
138 ScrollbarThumb {
139 border_radius: BorderRadius::all(px(4)),
140 border: px(1).all(),
141 },
142 ))),
143 ));
144 }),)),
145 )
146 }
147
148 /// Create a list row
149 fn text_row(caption: &str) -> impl Bundle {
150 (
151 Text::new(caption),
152 TextFont {
153 font_size: FontSize::Px(14.0),
154 ..default()
155 },
156 )
157 }
158
159 // Update the color of the scrollbar thumb.
160 fn update_scrollbar_thumb(
161 mut q_thumb: Query<
162 (&mut BackgroundColor, &Hovered, &ScrollbarDragState),
163 (
164 With<ScrollbarThumb>,
165 Or<(Changed<Hovered>, Changed<ScrollbarDragState>)>,
166 ),
167 >,
168 ) {
169 for (mut thumb_bg, Hovered(is_hovering), drag) in q_thumb.iter_mut() {
170 let color: Color = if *is_hovering || drag.dragging {
171 // If hovering, use a lighter color
172 colors::GRAY4
173 } else {
174 // Default color for the slider
175 colors::GRAY2
176 }
177 .into();
178
179 if thumb_bg.0 != color {
180 // Update the color of the thumb
181 thumb_bg.0 = color;
182 }
183 }
184 }
185
186 mod colors {
187 use bevy::color::Srgba;
188
189 pub const GRAY1: Srgba = Srgba::new(0.224, 0.224, 0.243, 1.0);
190 pub const GRAY2: Srgba = Srgba::new(0.486, 0.486, 0.529, 1.0);
191 pub const GRAY3: Srgba = Srgba::new(0.71, 0.71, 0.772, 1.0);
192 pub const GRAY4: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0);
193 }