1use 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 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 LetterSpacing::Px(0.0),
98 ));
99 });
100
101 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 LetterSpacing::Px(0.0),
132 ));
133 });
134
135 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 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}