Skip to main content

letter_spacing/
letter_spacing.rs

1//! This example demonstrates the `LetterSpacing` component in Bevy's text system.
2//!
3//! Use the left and right arrow keys to adjust the letter spacing of the text.
4
5use bevy::prelude::*;
6use bevy::text::{LetterSpacing, RemSize};
7
8#[derive(Component)]
9struct LetterSpacingLabel;
10
11#[derive(Component)]
12struct AnimatedLetterSpacing;
13
14#[derive(Resource, Default)]
15enum SpacingMode {
16    #[default]
17    Px,
18    Rem,
19}
20
21fn main() {
22    App::new()
23        .add_plugins(DefaultPlugins)
24        .init_resource::<SpacingMode>()
25        .add_systems(Startup, setup)
26        .add_systems(Update, (update_letter_spacing, toggle_mode))
27        .run();
28}
29
30fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
31    commands.spawn(Camera2d);
32
33    let font = asset_server.load("fonts/FiraSans-Bold.ttf");
34
35    commands
36        .spawn(Node {
37            width: percent(100),
38            height: percent(100),
39            ..default()
40        })
41        .with_children(|parent| {
42            parent
43                .spawn(Node {
44                    width: percent(100),
45                    height: percent(100),
46                    align_items: AlignItems::Center,
47                    padding: UiRect::axes(vw(5), vh(10)),
48                    row_gap: vh(6),
49                    flex_direction: FlexDirection::Column,
50                    ..default()
51                })
52                .with_children(|parent| {
53                    parent.spawn((
54                        Text::new("HELLO"),
55                        Underline,
56                        TextFont {
57                            font: font.clone().into(),
58                            font_size: FontSize::Vh(6.0),
59                            ..default()
60                        },
61                        Node {
62                            padding: vh(2).bottom(),
63                            ..default()
64                        },
65                    ));
66
67                    // Left justified
68                    parent
69                        .spawn(Node {
70                            flex_direction: FlexDirection::Column,
71                            width: percent(100.0),
72                            ..default()
73                        })
74                        .with_children(|parent| {
75                            parent.spawn((
76                                Text::new("Justify::Left"),
77                                TextFont {
78                                    font: font.clone().into(),
79                                    font_size: FontSize::Vh(2.0),
80                                    ..default()
81                                },
82                            ));
83                            parent.spawn((
84                                Text::new("letter spacing"),
85                                AnimatedLetterSpacing,
86                                TextLayout::justify(Justify::Left),
87                                TextFont {
88                                    font: font.clone().into(),
89                                    font_size: FontSize::Vh(6.0),
90                                    ..default()
91                                },
92                                Node {
93                                    width: percent(100.0),
94                                    ..default()
95                                },
96                                // Custom `LetterSpacing` can be added to any text entity as a component
97                                LetterSpacing::Px(0.0),
98                            ));
99                        });
100
101                    // Center justified
102                    parent
103                        .spawn(Node {
104                            flex_direction: FlexDirection::Column,
105                            width: percent(100.0),
106                            ..default()
107                        })
108                        .with_children(|parent| {
109                            parent.spawn((
110                                Text::new("Justify::Center"),
111                                TextFont {
112                                    font: font.clone().into(),
113                                    font_size: FontSize::Vh(2.0),
114                                    ..default()
115                                },
116                            ));
117                            parent.spawn((
118                                Text::new("letter spacing"),
119                                AnimatedLetterSpacing,
120                                TextLayout::justify(Justify::Center),
121                                TextFont {
122                                    font: font.clone().into(),
123                                    font_size: FontSize::Vh(6.0),
124                                    ..default()
125                                },
126                                Node {
127                                    width: percent(100.0),
128                                    ..default()
129                                },
130                                // Custom `LetterSpacing` can be added to any text entity as a component
131                                LetterSpacing::Px(0.0),
132                            ));
133                        });
134
135                    // Right justified
136                    parent
137                        .spawn(Node {
138                            flex_direction: FlexDirection::Column,
139                            width: percent(100.0),
140                            ..default()
141                        })
142                        .with_children(|parent| {
143                            parent.spawn((
144                                Text::new("Justify::Right"),
145                                TextFont {
146                                    font: font.clone().into(),
147                                    font_size: FontSize::Vh(2.0),
148                                    ..default()
149                                },
150                            ));
151                            parent.spawn((
152                                Text::new("letter spacing"),
153                                AnimatedLetterSpacing,
154                                TextLayout::justify(Justify::Right),
155                                TextFont {
156                                    font: font.clone().into(),
157                                    font_size: FontSize::Vh(6.0),
158                                    ..default()
159                                },
160                                Node {
161                                    width: percent(100.0),
162                                    ..default()
163                                },
164                                // Custom `LetterSpacing` can be added to any text entity as a component
165                                LetterSpacing::Px(0.0),
166                            ));
167                        });
168                });
169
170            parent.spawn((
171                Text::new("LetterSpacing::Px(0.0)"),
172                LetterSpacingLabel,
173                TextFont {
174                    font: font.clone().into(),
175                    font_size: FontSize::Vh(3.0),
176                    ..default()
177                },
178                Node {
179                    position_type: PositionType::Absolute,
180                    bottom: vh(2.0),
181                    left: vw(2.0),
182                    ..default()
183                },
184            ));
185
186            parent.spawn((
187                Text::new("← → to adjust   Space to toggle Px / Rem"),
188                TextFont {
189                    font: font.clone().into(),
190                    font_size: FontSize::Vh(2.5),
191                    ..default()
192                },
193                Node {
194                    position_type: PositionType::Absolute,
195                    bottom: vh(2.0),
196                    right: vw(2.0),
197                    ..default()
198                },
199            ));
200        });
201}
202
203fn toggle_mode(
204    keyboard: Res<ButtonInput<KeyCode>>,
205    mut mode: ResMut<SpacingMode>,
206    rem_size: Res<RemSize>,
207    mut query: Query<&mut LetterSpacing, With<AnimatedLetterSpacing>>,
208    mut label_query: Query<&mut Text, With<LetterSpacingLabel>>,
209) {
210    if !keyboard.just_pressed(KeyCode::Space) {
211        return;
212    }
213
214    for mut spacing in &mut query {
215        let new_spacing = match *spacing {
216            LetterSpacing::Px(v) => {
217                *mode = SpacingMode::Rem;
218                LetterSpacing::Rem(v / rem_size.0)
219            }
220            LetterSpacing::Rem(v) => {
221                *mode = SpacingMode::Px;
222                LetterSpacing::Px(v * rem_size.0)
223            }
224        };
225        *spacing = new_spacing;
226    }
227
228    for mut text in &mut label_query {
229        match *mode {
230            SpacingMode::Px => {
231                if let Some(LetterSpacing::Px(v)) = query.iter().next().copied() {
232                    text.0 = format!("LetterSpacing::Px({:.1})", v);
233                }
234            }
235            SpacingMode::Rem => {
236                if let Some(LetterSpacing::Rem(v)) = query.iter().next().copied() {
237                    text.0 = format!("LetterSpacing::Rem({:.2})", v);
238                }
239            }
240        }
241    }
242}
243
244fn update_letter_spacing(
245    keyboard: Res<ButtonInput<KeyCode>>,
246    mut query: Query<&mut LetterSpacing, With<AnimatedLetterSpacing>>,
247    mut label_query: Query<&mut Text, With<LetterSpacingLabel>>,
248) {
249    let delta = if keyboard.pressed(KeyCode::ArrowRight) {
250        0.5
251    } else if keyboard.pressed(KeyCode::ArrowLeft) {
252        -0.5
253    } else {
254        return;
255    };
256
257    for mut spacing in &mut query {
258        match *spacing {
259            LetterSpacing::Px(current) => {
260                let new_value = (current + delta).clamp(-100.0, 100.0);
261                *spacing = LetterSpacing::Px(new_value);
262                for mut text in &mut label_query {
263                    text.0 = format!("LetterSpacing::Px({:.1})", new_value);
264                }
265            }
266            LetterSpacing::Rem(current) => {
267                let new_value = (current + delta * 0.1).clamp(-10.0, 10.0);
268                *spacing = LetterSpacing::Rem(new_value);
269                for mut text in &mut label_query {
270                    text.0 = format!("LetterSpacing::Rem({:.2})", new_value);
271                }
272            }
273        }
274    }
275}