pub struct TextComponent {
pub text: String,
pub font_size: f32,
pub authored_font_size: f32,
pub wrap_at: usize,
pub authored_wrap_at: usize,
pub word_wrap: bool,
pub authored_word_wrap: bool,
pub word_wrap_tokens: Vec<String>,
pub authored_word_wrap_tokens: Vec<String>,
/* private fields */
}Expand description
Text component.
On registration, TextSystem expands this into per-glyph component trees.
Fields§
§text: String§font_size: f32Effective visual scale applied to spawned glyph quads.
This affects glyph rendering only; layout still measures text in glyph
columns. Style.font_size may temporarily override this value during
layout, while authored_font_size preserves the authored/default size.
Author-provided font size. Preserved when layout applies or removes a
style-driven override so the effective font_size can be restored.
wrap_at: usizeWrap after this many characters. This is the effective value used by
TextSystem for glyph layout. The layout pass may narrow it to fit the
containing block, but it never exceeds Self::authored_wrap_at.
Author-provided wrap cap. Captured at construction (and on decode)
and never modified by the layout pass. Layout uses this as the upper
bound when re-deriving wrap_at from the current container width —
so when the container grows again, wrap_at can be widened back up
to (but not past) authored_wrap_at.
Invariant: any future “set the wrap cap” mutation path (an MMS
wrap_at(N) setter, a Rust set_wrap helper, etc.) MUST update
both wrap_at and authored_wrap_at. Updating only wrap_at will
be silently undone by the next layout pass.
word_wrap: boolIf true, wrap only at whitespace boundaries (avoid breaking words). When false, wraps strictly by character count.
The layout pass may override this when the containing styled TC has
a word_wrap style — see Self::authored_word_wrap for the
authored/default value.
Author-provided word-wrap mode. Preserved when layout applies or removes
a style-driven override so the effective word_wrap can be restored.
word_wrap_tokens: Vec<String>Tokens after which wrapping is allowed when word_wrap == true.
This always includes whitespace tokens (space + tab) by default.
The layout pass may override this when the containing styled TC has
a word_wrap_tokens style — see Self::authored_word_wrap_tokens.
Author-provided word-wrap tokens. Preserved alongside
Self::authored_word_wrap for the same reason.
Implementations§
Source§impl TextComponent
impl TextComponent
pub const DEFAULT_FONT_SIZE: f32 = 1.0
Sourcepub const DEFAULT_WRAP_AT: usize = 0
pub const DEFAULT_WRAP_AT: usize = 0
Default authored wrap cap: 0 = no author cap. Layout still wraps to
fit the containing block; this default just means the author didn’t
impose an additional column limit. To set an explicit cap, use
with_wrap / with_word_wrap.
pub const DEFAULT_WORD_WRAP_TOKENS: [&'static str; 2]
Sourcepub fn new(text: impl Into<String>) -> Self
pub fn new(text: impl Into<String>) -> Self
Examples found in repository?
43fn spawn_text_label(universe: &mut engine::Universe, position: (f32, f32, f32), text: &str) {
44 // T_root { T_scale { TXT { filtering } } }
45 let text_root = universe
46 .world
47 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
48
49 let text_scale = universe
50 .world
51 .add_component(TransformComponent::new().with_scale(0.18, 0.18, 1.0));
52 let _ = universe.attach(text_root, text_scale);
53
54 let text_c = universe.world.add_component(TextComponent::new(text));
55 let _ = universe.attach(text_scale, text_c);
56
57 // Keep it crisp.
58 let filtering = universe
59 .world
60 .add_component(TextureFilteringComponent::nearest());
61 let _ = universe.attach(text_c, filtering);
62
63 // White label.
64 let color = universe
65 .world
66 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
67 let _ = universe.attach(text_c, color);
68
69 universe.add(text_root);
70}
71
72fn text_block_dimensions(text: &str) -> (usize, usize) {
73 let mut max_cols = 0usize;
74 let mut rows = 1usize;
75 let mut cur = 0usize;
76
77 for ch in text.chars() {
78 if ch == '\n' {
79 max_cols = max_cols.max(cur);
80 cur = 0;
81 rows += 1;
82 } else {
83 cur += 1;
84 }
85 }
86 max_cols = max_cols.max(cur);
87
88 (max_cols.max(1), rows.max(1))
89}
90
91fn spawn_text_label_with_bg(
92 universe: &mut engine::Universe,
93 position: (f32, f32, f32),
94 text: &str,
95 bg_opacity: f32,
96) {
97 // T_root {
98 // T_bg { R_bg { Color black, Opacity } }
99 // T_scale { TXT { filtering } }
100 // }
101
102 let text_root = universe
103 .world
104 .add_component(TransformComponent::new().with_position(position.0, position.1, position.2));
105
106 // Background quad (slightly behind the glyph quads).
107 let (cols, rows) = text_block_dimensions(text);
108 let text_scale = 0.18_f32;
109 let pad_x = 0.55_f32;
110 let pad_y = 0.45_f32;
111 let bg_w = cols as f32 * text_scale + pad_x;
112 let bg_h = rows as f32 * text_scale + pad_y;
113
114 let bg_t = universe.world.add_component(
115 TransformComponent::new()
116 .with_position(1.5, 0.0, -0.02)
117 .with_scale(bg_w, bg_h, 1.0),
118 );
119 let bg_r = universe.world.add_component(RenderableComponent::square());
120 let _ = universe.attach(text_root, bg_t);
121 let _ = universe.attach(bg_t, bg_r);
122
123 let bg_c = universe
124 .world
125 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
126 let _ = universe.attach(bg_r, bg_c);
127
128 let bg_o = universe
129 .world
130 .add_component(OpacityComponent::new().with_opacity(bg_opacity));
131 let _ = universe.attach(bg_r, bg_o);
132
133 let text_scale_t = universe
134 .world
135 .add_component(TransformComponent::new().with_scale(text_scale, text_scale, 1.0));
136 let _ = universe.attach(text_root, text_scale_t);
137
138 let text_c = universe.world.add_component(TextComponent::new(text));
139 let _ = universe.attach(text_scale_t, text_c);
140
141 // Keep it crisp.
142 let filtering = universe
143 .world
144 .add_component(TextureFilteringComponent::nearest());
145 let _ = universe.attach(text_c, filtering);
146
147 // Force the label into the transparent pass so it layers correctly with the background.
148 // (Pass selection currently does not consider texture alpha.)
149 let color = universe
150 .world
151 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 0.998));
152 let _ = universe.attach(text_c, color);
153
154 universe.add(text_root);
155}More examples
309fn spawn_label(
310 universe: &mut engine::Universe,
311 parent: engine::ecs::ComponentId,
312 pos: [f32; 3],
313 text: &str,
314) {
315 use engine::ecs::component::{
316 ColorComponent, EmissiveComponent, TextComponent, TextureFilteringComponent,
317 TransformComponent, TransparentCutoutComponent,
318 };
319
320 let root = universe.world.add_component(
321 TransformComponent::new()
322 .with_position(pos[0], pos[1], pos[2])
323 .with_scale(0.055, 0.055, 1.0),
324 );
325 let txt = universe.world.add_component(TextComponent::new(text));
326 let color = universe
327 .world
328 .add_component(ColorComponent::rgba(0.05, 0.05, 0.08, 1.0));
329 let emissive = universe.world.add_component(EmissiveComponent::on());
330 let filtering = universe
331 .world
332 .add_component(TextureFilteringComponent::nearest_magnification());
333 let cutout = universe
334 .world
335 .add_component(TransparentCutoutComponent::new());
336
337 let _ = universe.attach(parent, root);
338 let _ = universe.attach(root, txt);
339 let _ = universe.attach(txt, color);
340 let _ = universe.attach(txt, emissive);
341 let _ = universe.attach(txt, filtering);
342 let _ = universe.attach(txt, cutout);
343}3fn spawn_runtime_text(
4 universe: &mut engine::Universe,
5 owner: engine::ecs::ComponentId,
6 label: &str,
7) {
8 use engine::ecs::component::{
9 ColorComponent, EdgeInsets, SizeDimension, StyleComponent, TextComponent,
10 TransformComponent,
11 };
12
13 let root = universe
14 .world
15 .add_component_boxed_named(label, Box::new(TransformComponent::new()));
16 let style = universe.world.add_component_boxed_named(
17 format!("{label}_style"),
18 Box::new({
19 let mut style = StyleComponent::new();
20 style.margin = EdgeInsets::all(0.5);
21 style.padding = EdgeInsets::axes(0.75, 0.5);
22 style.width = SizeDimension::Auto;
23 style.background_color = Some([0.93, 0.88, 0.98, 1.0]);
24 style
25 }),
26 );
27 let text = universe
28 .world
29 .add_component_boxed_named(format!("{label}_text"), Box::new(TextComponent::new(label)));
30 let color = universe
31 .world
32 .add_component(ColorComponent::rgba(0.38, 0.14, 0.62, 1.0));
33
34 let _ = universe.world.add_child(root, style);
35 let _ = universe.world.add_child(root, text);
36 let _ = universe.world.add_child(text, color);
37
38 universe.attach(owner, root).expect("attach routed child");
39}108 fn spawn_text_style(
109 universe: &mut engine::Universe,
110 pos: [f32; 3],
111 scale: f32,
112 text: &str,
113 color: [f32; 4],
114 shadow: TextShadowComponent,
115 filtering: TextureFilteringComponent,
116 ) {
117 let root = universe.world.add_component(
118 TransformComponent::new()
119 .with_position(pos[0], pos[1], pos[2])
120 .with_scale(scale, scale, 1.0),
121 );
122
123 // Color must be an ancestor of the glyph renderables.
124 let color_id = universe
125 .world
126 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
127 let _ = universe.attach(root, color_id);
128
129 let text_id = universe.world.add_component(TextComponent::new(text));
130 let _ = universe.attach(color_id, text_id);
131
132 // Route into cutout pass for cleaner edges.
133 let cutout = universe
134 .world
135 .add_component(TransparentCutoutComponent::new());
136 let _ = universe.attach(text_id, cutout);
137
138 // Use the same atlas as the debug quad.
139 let tex = universe
140 .world
141 .add_component(TextureComponent::with_uri("assets/textures/font.dds"));
142 let _ = universe.attach(text_id, tex);
143
144 let shadow_id = universe.world.add_component(shadow);
145 let _ = universe.attach(text_id, shadow_id);
146
147 let filtering_id = universe.world.add_component(filtering);
148 let _ = universe.attach(text_id, filtering_id);
149
150 universe.add(root);
151 }5fn spawn_runtime_text(
6 universe: &mut engine::Universe,
7 owner: engine::ecs::ComponentId,
8 label: &str,
9) {
10 use engine::ecs::component::{
11 ColorComponent, EdgeInsets, SizeDimension, StyleComponent, TextComponent,
12 TransformComponent,
13 };
14
15 let root = universe.world.add_component_boxed_named(
16 label,
17 Box::new(TransformComponent::new().with_position(0.0, 0.0, 0.2)),
18 );
19 let style = universe.world.add_component_boxed_named(
20 format!("{label}_style"),
21 Box::new({
22 let mut style = StyleComponent::new();
23 style.margin = EdgeInsets::axes(0.25, 0.25);
24 style.padding = EdgeInsets::axes(0.5, 0.5);
25 style.height = SizeDimension::GlyphUnits(2.5);
26 style.width = SizeDimension::Auto;
27 style.background_color = Some([1.0, 0.80, 0.80, 1.0]);
28 style
29 }),
30 );
31 let text_root = universe.world.add_component_boxed_named(
32 format!("{label}_text_root"),
33 Box::new(TransformComponent::new().with_position(0.0, 0.0, 0.2)),
34 );
35 let text = universe
36 .world
37 .add_component_boxed_named(format!("{label}_text"), Box::new(TextComponent::new(label)));
38 let color = universe
39 .world
40 .add_component(ColorComponent::rgba(0.40, 0.05, 0.05, 1.0));
41
42 let _ = universe.world.add_child(root, style);
43 let _ = universe.world.add_child(root, text_root);
44 let _ = universe.world.add_child(text_root, text);
45 let _ = universe.world.add_child(text, color);
46
47 universe.attach(owner, root).expect("attach routed child");
48}16fn spawn_row(
17 universe: &mut engine::Universe,
18 scrolling: engine::ecs::ComponentId,
19 index: usize,
20) -> engine::ecs::ComponentId {
21 use engine::ecs::component::{
22 ColorComponent, RenderableComponent, TextComponent, TransformComponent,
23 };
24
25 let label = format!("row_{index:02}");
26 let row = universe.world.add_component_boxed_named(
27 label.clone(),
28 Box::new(TransformComponent::new().with_position(0.0, -(index as f32) * 1.15, 0.05)),
29 );
30 let panel = universe.world.add_component_boxed_named(
31 format!("{label}_panel"),
32 Box::new(
33 TransformComponent::new()
34 .with_position(2.3, -0.45, 0.0)
35 .with_scale(4.6, 0.9, 1.0),
36 ),
37 );
38 let panel_renderable = universe.world.add_component_boxed_named(
39 format!("{label}_rend"),
40 Box::new(RenderableComponent::square()),
41 );
42 let panel_color = universe
43 .world
44 .add_component(ColorComponent::rgba(0.98, 0.94, 0.78, 1.0));
45
46 let text_anchor = universe.world.add_component_boxed_named(
47 format!("{label}_text_anchor"),
48 Box::new(
49 TransformComponent::new()
50 .with_position(0.35, -0.38, 0.02)
51 .with_scale(0.11, 0.11, 0.11),
52 ),
53 );
54 let text = universe.world.add_component_boxed_named(
55 format!("{label}_text"),
56 Box::new(TextComponent::new(label.clone())),
57 );
58 let text_color = universe
59 .world
60 .add_component(ColorComponent::rgba(0.20, 0.16, 0.08, 1.0));
61
62 let _ = universe.world.add_child(row, panel);
63 let _ = universe.world.add_child(panel, panel_renderable);
64 let _ = universe.world.add_child(panel_renderable, panel_color);
65 let _ = universe.world.add_child(row, text_anchor);
66 let _ = universe.world.add_child(text_anchor, text);
67 let _ = universe.world.add_child(text, text_color);
68
69 universe.attach(scrolling, row).expect("attach scroll row");
70 row
71}Sourcepub fn with_wrap(text: impl Into<String>, wrap_at: usize) -> Self
pub fn with_wrap(text: impl Into<String>, wrap_at: usize) -> Self
Examples found in repository?
60fn main() {
61 mittens_engine::example_support::ensure_model_assets();
62 utils::logger::init();
63
64 // Usage:
65 // cargo run --example folder-text -- [folder] [spacing]
66 // Defaults:
67 // folder=src spacing=0.3
68 let mut args = std::env::args().skip(1);
69 let folder = args.next().unwrap_or_else(|| "src".to_string());
70 let spacing: f32 = args
71 .next()
72 .and_then(|s| s.parse::<f32>().ok())
73 .unwrap_or(1.0);
74
75 // Safety caps: this demo can explode the ECS if we load huge projects.
76 const MAX_FILES: usize = 42;
77 const MAX_CHARS_PER_FILE: usize = 10_000;
78 const WRAP_AT: usize = 90;
79 const TEXT_SCALE: f32 = 0.01;
80
81 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
82 let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
83 let scan_root = {
84 let p = PathBuf::from(&folder);
85 if p.is_absolute() {
86 p
87 } else {
88 // Resolve relative paths from the crate root, not the process CWD.
89 manifest_dir.join(p)
90 }
91 };
92 eprintln!("[folder-text] cwd={:?}", cwd);
93 eprintln!("[folder-text] manifest_dir={:?}", manifest_dir);
94 eprintln!("[folder-text] scan_root={:?}", scan_root);
95
96 let mut files = Vec::new();
97 collect_rs_files(&scan_root, &mut files);
98 files.sort();
99
100 if files.is_empty() {
101 eprintln!(
102 "[folder-text] No .rs files found under folder={:?} (resolved={:?})",
103 folder, scan_root
104 );
105 }
106
107 let files: Vec<PathBuf> = files.into_iter().take(MAX_FILES).collect();
108 eprintln!(
109 "[folder-text] Loaded {} file(s) from {:?} (spacing={})",
110 files.len(),
111 folder,
112 spacing
113 );
114
115 #[derive(Debug, Clone)]
116 struct FileEntry {
117 path: PathBuf,
118 display_text: String,
119 max_cols: usize,
120 rows: usize,
121 }
122
123 // Group files by folder.
124 // Layout:
125 // - each folder becomes a column along +X
126 // - within a folder, files stack along +Y (8 high)
127 // - after 8, additional files start a new stack further along +Z
128 let mut files_by_folder: BTreeMap<PathBuf, Vec<FileEntry>> = BTreeMap::new();
129 for path in files {
130 let Ok(content) = std::fs::read_to_string(&path) else {
131 eprintln!("[folder-text] Failed to read {:?}", path);
132 continue;
133 };
134
135 let mut display_text = format!(
136 "// {}\n\n{}",
137 path.strip_prefix(&manifest_dir).unwrap_or(&path).display(),
138 content
139 );
140
141 if display_text.chars().count() > MAX_CHARS_PER_FILE {
142 display_text = display_text
143 .chars()
144 .take(MAX_CHARS_PER_FILE)
145 .collect::<String>();
146 display_text.push_str("\n\n// ... truncated ...\n");
147 }
148
149 let (max_cols, rows) = measure_text_bounds(&display_text, WRAP_AT);
150
151 let folder_key = path.parent().unwrap_or(&scan_root).to_path_buf();
152 files_by_folder
153 .entry(folder_key)
154 .or_default()
155 .push(FileEntry {
156 path,
157 display_text,
158 max_cols,
159 rows,
160 });
161 }
162
163 let column_count = files_by_folder.len().max(1);
164
165 let world = engine::ecs::World::default();
166 let mut universe = engine::Universe::new(world);
167
168 let bg_r = 0.25;
169 let bg_g = 0.25;
170 let bg_b = 0.25;
171 let background = universe
172 .world
173 .add_component(engine::ecs::component::BackgroundColorComponent::new());
174 let background_c = universe
175 .world
176 .add_component(engine::ecs::component::ColorComponent::rgba(
177 bg_r, bg_g, bg_b, 1.00,
178 ));
179 let _ = universe.world.add_child(background, background_c);
180 universe.add(background);
181
182 let ambient = universe
183 .world
184 .add_component(engine::ecs::component::AmbientLightComponent::rgb(
185 bg_r, bg_g, bg_b,
186 ));
187 universe.add(ambient);
188
189 // Input-driven camera rig.
190 // Topology: I { T { C3D } }
191 let input = universe
192 .world
193 .add_component(engine::ecs::component::InputComponent::new().with_speed(1.5));
194
195 // Center the camera horizontally over the spawned columns.
196 let center_x = if column_count <= 1 {
197 0.0
198 } else {
199 ((column_count - 1) as f32) * spacing * 0.5
200 };
201
202 // Bring camera closer: these text blocks are tiny.
203 let rig_transform = universe.world.add_component(
204 engine::ecs::component::TransformComponent::new().with_position(center_x, 0.0, 1.2),
205 );
206 let camera3d = universe
207 .world
208 .add_component(engine::ecs::component::Camera3DComponent::new());
209 let input_mode = universe.world.add_component(
210 engine::ecs::component::InputTransformModeComponent::forward_z().with_roll_axis_y(),
211 );
212 let _ = universe.attach(input, input_mode);
213 let _ = universe.attach(input, rig_transform);
214 let _ = universe.attach(rig_transform, camera3d);
215
216 // Topology: I { T { C3D } } — add a small camera-attached controls hint.
217 example_util::spawn_desktop_camera_controls_hint(&mut universe, rig_transform);
218 universe.add(input);
219
220 // Big floor plane under everything.
221 // RenderableComponent::square() is an XY quad facing +Z; rotate it to XZ facing +Y.
222 let max_stacks = files_by_folder
223 .values()
224 .map(|v| (v.len() + 7) / 8)
225 .max()
226 .unwrap_or(1)
227 .max(1);
228 let total_width = (column_count as f32) * spacing;
229 let total_depth = (max_stacks as f32) * spacing;
230 let floor_w = total_width.max(10.0);
231 let floor_h = total_depth.max(10.0);
232 let floor_transform = universe.world.add_component(
233 engine::ecs::component::TransformComponent::new()
234 .with_position(0.0, -2.0, 0.0)
235 .with_rotation_euler(-std::f32::consts::FRAC_PI_2, 0.0, 0.0)
236 .with_scale(floor_w, floor_h, 1.0),
237 );
238 let floor_renderable = universe
239 .world
240 .add_component(engine::ecs::component::RenderableComponent::square());
241 let floor_color = universe
242 .world
243 .add_component(engine::ecs::component::ColorComponent::rgba(
244 0.88, 0.88, 0.88, 1.0,
245 ));
246 let _ = universe.attach(floor_transform, floor_renderable);
247 let _ = universe.attach(floor_renderable, floor_color);
248 universe.add(floor_transform);
249
250 // 4 red cubes around the perimeter of the world (easy visual anchors).
251 fn spawn_red_cube(universe: &mut engine::Universe, x: f32, y: f32, z: f32, s: f32) {
252 let t = universe.world.add_component(
253 engine::ecs::component::TransformComponent::new()
254 .with_position(x, y, z)
255 .with_scale(s, s, s),
256 );
257 let r = universe
258 .world
259 .add_component(engine::ecs::component::RenderableComponent::cube());
260 let c = universe
261 .world
262 .add_component(engine::ecs::component::ColorComponent::rgba(
263 1.0, 0.0, 0.0, 1.0,
264 ));
265
266 let light_transform = universe.world.add_component(
267 engine::ecs::component::TransformComponent::new().with_position(0.0, s * 0.75, 0.0),
268 );
269 let light = universe.world.add_component(
270 engine::ecs::component::PointLightComponent::new()
271 .with_intensity(1.5)
272 .with_distance(20.0)
273 .with_color(1.0, 0.0, 0.0),
274 );
275
276 let _ = universe.attach(t, r);
277 let _ = universe.attach(r, c);
278 let _ = universe.attach(t, light_transform);
279 let _ = universe.attach(light_transform, light);
280
281 universe.add(t);
282 }
283
284 let p = 1.5;
285 let s = 0.25;
286 spawn_red_cube(&mut universe, -p, -p, 0.0, s);
287 spawn_red_cube(&mut universe, p, -p, 0.0, s);
288 spawn_red_cube(&mut universe, -p, p, 0.0, s);
289 spawn_red_cube(&mut universe, p, p, 0.0, s);
290
291 let start_x = -center_x;
292 let stack_depth = spacing;
293 let row_gap_world = 0.35;
294
295 // One global point light at the top of the overall text "tower".
296 let pad_y = 4.0;
297 let global_max_panel_h_world = files_by_folder
298 .values()
299 .flat_map(|v| v.iter())
300 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
301 .fold(0.0_f32, f32::max)
302 .max(0.6);
303 let global_slot_h_world = global_max_panel_h_world + row_gap_world;
304 let tower_top_y = (7.0 * global_slot_h_world) + 4.0;
305 let tower_center_z = total_depth * 0.5;
306
307 let tower_light_transform = universe.world.add_component(
308 engine::ecs::component::TransformComponent::new().with_position(
309 0.0,
310 tower_top_y,
311 tower_center_z,
312 ),
313 );
314 let tower_light = universe.world.add_component(
315 engine::ecs::component::PointLightComponent::new()
316 .with_intensity(2.0)
317 .with_distance(120.0)
318 .with_color(1.0, 1.0, 1.0),
319 );
320 let _ = universe.attach(tower_light_transform, tower_light);
321 universe.add(tower_light_transform);
322
323 for (col_idx, (_folder, mut entries)) in files_by_folder.into_iter().enumerate() {
324 entries.sort_by(|a, b| a.path.cmp(&b.path));
325
326 // Slot height (world units) based on the tallest panel in this folder.
327 let pad_y = 4.0;
328 let max_panel_h_world = entries
329 .iter()
330 .map(|e| ((e.rows as f32) + pad_y) * TEXT_SCALE)
331 .fold(0.0_f32, f32::max)
332 .max(0.6);
333 let slot_h_world = max_panel_h_world + row_gap_world;
334
335 let col_x = start_x + (col_idx as f32) * spacing;
336
337 for (i, entry) in entries.into_iter().enumerate() {
338 let row = (i % 8) as f32;
339 let stack = (i / 8) as f32;
340 let file_y = row * slot_h_world;
341 let file_z = stack * stack_depth;
342
343 let file_group = universe.world.add_component(
344 engine::ecs::component::TransformComponent::new()
345 .with_position(col_x, file_y, file_z),
346 );
347
348 // Text subtree (tiny scale).
349 let file_root = universe.world.add_component(
350 engine::ecs::component::TransformComponent::new()
351 .with_position(0.0, 1.0, 0.0)
352 .with_scale(TEXT_SCALE, TEXT_SCALE, 1.0)
353 .with_rotation_euler(0.0, std::f32::consts::PI / 6.0, 0.0),
354 );
355 let _ = universe.attach(file_group, file_root);
356
357 // Background panel behind the text.
358 // Size is based on wrapped line count (matches TextSystem's strict wrapping behavior).
359 let (max_cols, rows) = (entry.max_cols, entry.rows);
360 let pad_x = 4.0;
361 let pad_y = 4.0;
362 let w = (max_cols as f32) + pad_x;
363 let h = (rows as f32) + pad_y;
364
365 // Text glyph quads are centered at integer (col,row) positions and are 1x1, so the
366 // text's AABB (in text-space) is roughly:
367 // x: [-0.5, max_cols-0.5]
368 // y: [-(rows-1)-0.5, 0.5]
369 // Centering the background quad at those midpoints keeps it aligned as text grows.
370 let bg_x = (max_cols as f32 - 1.0) * 0.5;
371 let bg_y = -((rows as f32 - 1.0) * 0.5);
372
373 let bg_transform = universe.world.add_component(
374 engine::ecs::component::TransformComponent::new()
375 .with_position(bg_x, bg_y, -0.05)
376 .with_scale(w, h, 1.0),
377 );
378 let bg_renderable = universe
379 .world
380 .add_component(engine::ecs::component::RenderableComponent::square());
381 let bg_quant = universe.world.add_component(
382 engine::ecs::component::LightQuantizationComponent::steps(5.0),
383 );
384 let bg_color =
385 universe
386 .world
387 .add_component(engine::ecs::component::ColorComponent::rgba(
388 0.2, 0.2, 0.2, 1.0,
389 ));
390 let _ = universe.attach(file_root, bg_transform);
391 let _ = universe.attach(bg_transform, bg_renderable);
392 let _ = universe.attach(bg_renderable, bg_quant);
393 let _ = universe.attach(bg_renderable, bg_color);
394
395 let text =
396 universe
397 .world
398 .add_component(engine::ecs::component::TextComponent::with_wrap(
399 entry.display_text,
400 WRAP_AT,
401 ));
402 let cutout = universe
403 .world
404 .add_component(engine::ecs::component::TransparentCutoutComponent::new());
405 let filtering = universe.world.add_component(
406 engine::ecs::component::TextureFilteringComponent::nearest_magnification(),
407 );
408 // let color = universe
409 // .world
410 // .add_component(engine::ecs::component::ColorComponent::rgba(0.7, 0.7, 1.0, 1.0));
411 let emissive = universe
412 .world
413 .add_component(engine::ecs::component::EmissiveComponent::on());
414 let _ = universe.attach(file_root, text);
415 let _ = universe.attach(text, cutout);
416 let _ = universe.attach(text, filtering);
417 //let _ = universe.world.add_child(text, color);
418 let _ = universe.attach(text, emissive);
419
420 universe.add(file_group);
421 }
422 }
423
424 // Process init-time registrations (Text expands into glyph subtrees here).
425 universe.systems.process_commands(
426 &mut universe.world,
427 &mut universe.visuals,
428 &mut universe.render_assets,
429 &mut universe.command_queue,
430 );
431
432 engine::Windowing::run_app(universe).expect("Windowing failed");
433}Sourcepub fn with_word_wrap(text: impl Into<String>, wrap_at: usize) -> Self
pub fn with_word_wrap(text: impl Into<String>, wrap_at: usize) -> Self
Word-wrap (prefer wrapping at whitespace) aiming for wrap_at characters.
If the line exceeds wrap_at and there was no whitespace to wrap at,
the line will continue (words are not broken).
Examples found in repository?
101 fn spawn_text(universe: &mut engine::Universe, pos: (f32, f32, f32), scale: f32, text: &str) {
102 let tx = universe.world.add_component(
103 engine::ecs::component::TransformComponent::new()
104 .with_position(pos.0, pos.1, pos.2)
105 .with_scale(scale, scale, 1.0),
106 );
107 let t =
108 universe
109 .world
110 .add_component(engine::ecs::component::TextComponent::with_word_wrap(
111 text, 38,
112 ));
113 let _ = universe.attach(tx, t);
114 universe.add(tx);
115 }More examples
199 fn spawn_text(
200 universe: &mut engine::Universe,
201 pos: (f32, f32, f32),
202 scale: f32,
203 wrap_cols: usize,
204 text: &str,
205 ) {
206 let tx = universe.world.add_component(
207 engine::ecs::component::TransformComponent::new()
208 .with_position(pos.0, pos.1, pos.2)
209 .with_scale(scale, scale, 1.0),
210 );
211 let t =
212 universe
213 .world
214 .add_component(engine::ecs::component::TextComponent::with_word_wrap(
215 text, wrap_cols,
216 ));
217 let _ = universe.attach(tx, t);
218
219 // TextSystem looks for an immediate TextureFilteringComponent child.
220 let filtering = universe
221 .world
222 .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
223 let _ = universe.attach(t, filtering);
224
225 // TextSystem also supports styling from immediate Emissive children.
226 let emissive = universe
227 .world
228 .add_component(engine::ecs::component::EmissiveComponent::on());
229 let _ = universe.attach(t, emissive);
230
231 universe.add(tx);
232 }
233
234 fn spawn_emissive_cube(
235 universe: &mut engine::Universe,
236 parent: engine::ecs::ComponentId,
237 pos: (f32, f32, f32),
238 scale: f32,
239 rgba: [f32; 4],
240 ) {
241 let tx = universe.world.add_component(
242 engine::ecs::component::TransformComponent::new()
243 .with_position(pos.0, pos.1, pos.2)
244 .with_scale(scale, scale, scale),
245 );
246 let r = universe
247 .world
248 .add_component(engine::ecs::component::RenderableComponent::cube());
249 let c = universe
250 .world
251 .add_component(engine::ecs::component::ColorComponent::rgba(
252 rgba[0], rgba[1], rgba[2], rgba[3],
253 ));
254 let e = universe
255 .world
256 .add_component(engine::ecs::component::EmissiveComponent::on());
257 let _ = universe.attach(parent, tx);
258 let _ = universe.attach(tx, r);
259 let _ = universe.attach(r, c);
260 let _ = universe.attach(r, e);
261 }
262
263 fn spawn_op_cube(
264 universe: &mut engine::Universe,
265 parent: engine::ecs::ComponentId,
266 pos: (f32, f32, f32),
267 scale: f32,
268 base_rgba: [f32; 4],
269 ) -> engine::ecs::ComponentId {
270 let tx = universe.world.add_component(
271 engine::ecs::component::TransformComponent::new()
272 .with_position(pos.0, pos.1, pos.2)
273 .with_scale(scale, scale, scale),
274 );
275 let r = universe
276 .world
277 .add_component(engine::ecs::component::RenderableComponent::cube());
278 let c = universe
279 .world
280 .add_component(engine::ecs::component::ColorComponent::rgba(
281 base_rgba[0],
282 base_rgba[1],
283 base_rgba[2],
284 base_rgba[3],
285 ));
286 let e = universe
287 .world
288 .add_component(engine::ecs::component::EmissiveComponent::on());
289
290 let _ = universe.attach(parent, tx);
291 let _ = universe.attach(tx, r);
292 let _ = universe.attach(r, c);
293 let _ = universe.attach(r, e);
294
295 tx
296 }
297
298 // --- HUD / lane (single track) ---
299 let lane_x = -2.6_f32;
300 let lane_title_z = -0.4_f32;
301 let lane_cfg_z = -0.4_f32;
302 let lane_pat_z = -1.3_f32;
303 let lane_graph_z = -1.15_f32;
304
305 // Content for pattern/chain/graph starts at x=0; keep section labels aligned there.
306 // This also keeps them from overlapping the config block (which is anchored at lane_x).
307 let lane_labels_x = lane_x + 1.6_f32;
308
309 let track_a_y = 0.9_f32;
310
311 spawn_text(
312 &mut universe,
313 (lane_x, track_a_y + 0.55, lane_title_z),
314 0.09,
315 42,
316 "Track A: AudioOscillator::square()",
317 );
318 spawn_text(
319 &mut universe,
320 (lane_x, track_a_y + 0.25, lane_cfg_z),
321 0.07,
322 48,
323 "oscillators=1\nfrequency_hz=110.0\namplitude=0.12\nenabled=false\nlookahead=0.10s",
324 );
325
326 let viz_root = universe.world.add_component(
327 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
328 );
329 universe.add(viz_root);
330
331 println!("[audio-graph-example] viz root added");
332
333 // Small identifier cubes next to titles.
334 spawn_emissive_cube(
335 &mut universe,
336 viz_root,
337 (lane_x - 0.28, track_a_y + 0.55, lane_title_z),
338 0.16,
339 [0.85, 0.40, 1.00, 1.0],
340 );
341
342 // Pattern roots so we can reset via one SetColor action.
343 let track_a_pattern_root = universe.world.add_component(
344 engine::ecs::component::TransformComponent::new().with_position(0.0, 0.0, 0.0),
345 );
346 let _ = universe.attach(viz_root, track_a_pattern_root);
347
348 // Labels for pattern/chain/graph sections.
349 spawn_text(
350 &mut universe,
351 (lane_labels_x, track_a_y, lane_title_z),
352 0.06,
353 42,
354 "pattern",
355 );
356 spawn_text(
357 &mut universe,
358 (lane_labels_x, track_a_y - 0.40, lane_title_z),
359 0.06,
360 42,
361 "graph",
362 );
363
364 // --- Processing chain + graph visualization (compiled graph → cubes + labels) ---
365 use engine::ecs::system::audio_graph_compiler::{
366 AudioGraphCompiler, AudioGraphNode, AudioGraphNodeKind,
367 };
368
369 fn effect_grey_for_depth(depth: usize) -> f32 {
370 // depth=1 => medium grey; deeper => lighter, capped.
371 let base = 0.55;
372 let step = 0.13;
373 let d = depth.saturating_sub(1) as f32;
374 (base + step * d).min(0.92)
375 }
376
377 fn node_rgba(node: &AudioGraphNode, depth: usize) -> [f32; 4] {
378 match node.kind {
379 AudioGraphNodeKind::OscillatorSource { .. } => [1.0, 0.78, 0.22, 1.0],
380 _ => {
381 let g = effect_grey_for_depth(depth);
382 [g, g, g, 1.0]
383 }
384 }
385 }
386
387 fn node_label(node: &AudioGraphNode) -> String {
388 match &node.kind {
389 AudioGraphNodeKind::OscillatorSource { voices } => {
390 format!("OscillatorSource voices={voices}")
391 }
392 AudioGraphNodeKind::Gain { gain } => {
393 format!("Gain gain={gain:.3}")
394 }
395 AudioGraphNodeKind::LowPass {
396 cutoff_hz,
397 resonance,
398 } => {
399 format!("LowPass cutoff={cutoff_hz:.1}Hz res={resonance:.3}")
400 }
401 AudioGraphNodeKind::BandPass {
402 center_hz,
403 bandwidth_octaves,
404 resonance,
405 } => {
406 format!(
407 "BandPass center={center_hz:.1}Hz bw={bandwidth_octaves:.3}oct res={resonance:.3}"
408 )
409 }
410 AudioGraphNodeKind::HighPass {
411 cutoff_hz,
412 resonance,
413 } => {
414 format!("HighPass cutoff={cutoff_hz:.1}Hz res={resonance:.3}")
415 }
416 AudioGraphNodeKind::Limiter {
417 attack_ms,
418 release_ms,
419 threshold,
420 } => {
421 format!("Limiter atk={attack_ms:.1}ms rel={release_ms:.1}ms thr={threshold:.3}")
422 }
423 AudioGraphNodeKind::ClipSource => "ClipSource".to_string(),
424 }
425 }
426
427 fn compute_layout(
428 node: &AudioGraphNode,
429 depth: usize,
430 x_cursor: &mut i32,
431 out: &mut std::collections::HashMap<*const AudioGraphNode, (i32, usize)>,
432 ) -> i32 {
433 // Depth-only layout:
434 // - keep children directly below their parent on X
435 // - use Z sibling offsets (in spawn_graph_tree) to show branching
436 let _ = x_cursor;
437 for ch in node.children.iter() {
438 let _ = compute_layout(ch, depth + 1, x_cursor, out);
439 }
440
441 let my_x = 0;
442 out.insert(node as *const AudioGraphNode, (my_x, depth));
443 my_x
444 }
445
446 fn spawn_graph_tree(
447 universe: &mut engine::Universe,
448 parent: engine::ecs::ComponentId,
449 node: &AudioGraphNode,
450 bp_component: Option<engine::ecs::ComponentId>,
451 bp_label_out: &mut Option<engine::ecs::ComponentId>,
452 origin: (f32, f32, f32),
453 layout: &std::collections::HashMap<*const AudioGraphNode, (i32, usize)>,
454 dx: f32,
455 dy: f32,
456 cube_scale: f32,
457 sibling_index: usize,
458 sibling_count: usize,
459 ) {
460 let Some((x_unit, depth)) = layout.get(&(node as *const AudioGraphNode)).copied() else {
461 return;
462 };
463
464 let x = origin.0 + (x_unit as f32) * dx;
465 let y = origin.1 - (depth as f32) * dy;
466
467 // Push siblings “behind” each other along Z to reduce overlap between
468 // a sibling's cube and another node's label.
469 let dz_sibling = 0.18;
470 let z = origin.2 - (sibling_index as f32) * dz_sibling;
471
472 // Keep text slightly in front of its cube.
473 let z_text = z + 0.03;
474
475 let rgba = node_rgba(node, depth);
476 let cube_tx = spawn_op_cube(universe, parent, (x, y, z), cube_scale, rgba);
477 let _ = cube_tx;
478
479 // Label next to the cube.
480 let label = node_label(node);
481 let tx = universe.world.add_component(
482 engine::ecs::component::TransformComponent::new()
483 .with_position(x + 0.14, y + 0.02, z_text)
484 .with_scale(0.06, 0.06, 1.0),
485 );
486 let t =
487 universe
488 .world
489 .add_component(engine::ecs::component::TextComponent::with_word_wrap(
490 &label, 25,
491 ));
492 let _ = universe.attach(parent, tx);
493 let _ = universe.attach(tx, t);
494
495 if bp_component == Some(node.component) {
496 *bp_label_out = Some(t);
497 }
498
499 let filtering = universe
500 .world
501 .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
502 let _ = universe.attach(t, filtering);
503
504 let emissive = universe
505 .world
506 .add_component(engine::ecs::component::EmissiveComponent::on());
507 let _ = universe.attach(t, emissive);
508
509 universe.add(tx);
510
511 // Mix/branch label if branching.
512 if node.children.len() > 1 {
513 let mut weights: Vec<f32> = Vec::with_capacity(node.children.len());
514 for i in 0..node.children.len() {
515 let w = node
516 .mix
517 .as_ref()
518 .map(|m| m.weights.get(i).copied().unwrap_or(1.0))
519 .unwrap_or(1.0);
520 weights.push(w);
521 }
522 let mix_label = if node.mix.is_some() {
523 format!("Mix weights={weights:?}")
524 } else {
525 format!("Mix <implicit> w={weights:?}")
526 };
527 let mix_tx = universe.world.add_component(
528 engine::ecs::component::TransformComponent::new()
529 .with_position(x + 0.14, y - 0.11, z_text)
530 .with_scale(0.05, 0.05, 1.0),
531 );
532 let mix_t = universe.world.add_component(
533 engine::ecs::component::TextComponent::with_word_wrap(&mix_label, 25),
534 );
535 let _ = universe.attach(parent, mix_tx);
536 let _ = universe.attach(mix_tx, mix_t);
537
538 let filtering = universe
539 .world
540 .add_component(engine::ecs::component::TextureFilteringComponent::nearest());
541 let _ = universe.attach(mix_t, filtering);
542
543 let emissive = universe
544 .world
545 .add_component(engine::ecs::component::EmissiveComponent::on());
546 let _ = universe.attach(mix_t, emissive);
547
548 universe.add(mix_tx);
549 }
550
551 let child_count = node.children.len().max(1);
552 for (i, ch) in node.children.iter().enumerate() {
553 // Each node's children form a sibling group; offset them in Z.
554 // For the root node, sibling_index is 0.
555 let _ = sibling_count;
556 spawn_graph_tree(
557 universe,
558 parent,
559 ch,
560 bp_component,
561 bp_label_out,
562 origin,
563 layout,
564 dx,
565 dy,
566 cube_scale,
567 i,
568 child_count,
569 );
570 }
571 }276fn spawn_button(
277 universe: &mut engine::Universe,
278 pos: [f32; 3],
279 middle_wh: [f32; 2],
280 border: ButtonBorder,
281 text: &str,
282 text_scale: f32,
283 color: [f32; 4],
284 background_color: [f32; 4],
285) -> engine::ecs::ComponentId {
286 use engine::ecs::component::{
287 ColorComponent, TextComponent, TextureFilteringComponent, TransformComponent,
288 TransparentCutoutComponent,
289 };
290
291 let button_root = universe
292 .world
293 .add_component(TransformComponent::new().with_position(pos[0], pos[1], pos[2]));
294
295 // Frame: static border around the button.
296 let frame_root = universe.world.add_component(TransformComponent::new());
297 let frame_color = universe
298 .world
299 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
300 let _ = universe.attach(button_root, frame_root);
301 let _ = universe.attach(frame_root, frame_color);
302
303 // Cap: pressable face + text.
304 let cap_raised_z = 0.030;
305 let cap_pressed_z = 0.000;
306 let cap_root = universe
307 .world
308 .add_component(TransformComponent::new().with_position(0.0, 0.0, cap_raised_z));
309 let _ = universe.attach(button_root, cap_root);
310
311 let face_color = universe.world.add_component(ColorComponent::rgba(
312 background_color[0],
313 background_color[1],
314 background_color[2],
315 background_color[3],
316 ));
317 let _ = universe.attach(cap_root, face_color);
318
319 // Geometry.
320 let depth = 0.025;
321 let middle_w = middle_wh[0];
322 let middle_h = middle_wh[1];
323
324 // Border pieces.
325 let full_w = middle_w + border.left + border.right;
326 let full_h = middle_h + border.top + border.bottom;
327
328 // Left / right.
329 let _left = spawn_raycastable_cube(
330 universe,
331 frame_color,
332 [-(middle_w * 0.5 + border.left * 0.5), 0.0, 0.0],
333 [border.left, full_h, depth],
334 );
335 let _right = spawn_raycastable_cube(
336 universe,
337 frame_color,
338 [(middle_w * 0.5 + border.right * 0.5), 0.0, 0.0],
339 [border.right, full_h, depth],
340 );
341
342 // Top / bottom.
343 let _top = spawn_raycastable_cube(
344 universe,
345 frame_color,
346 [0.0, (middle_h * 0.5 + border.top * 0.5), 0.0],
347 [full_w, border.top, depth],
348 );
349 let _bottom = spawn_raycastable_cube(
350 universe,
351 frame_color,
352 [0.0, -(middle_h * 0.5 + border.bottom * 0.5), 0.0],
353 [full_w, border.bottom, depth],
354 );
355
356 // Face (pressable center).
357 let _face = spawn_raycastable_cube(
358 universe,
359 face_color,
360 [0.0, 0.0, 0.0],
361 [middle_w, middle_h, depth],
362 );
363
364 // Text. (Heuristic centering; TextComponent is top-left anchored today.)
365 let text_root = universe.world.add_component(
366 TransformComponent::new()
367 .with_position(0.0, 0.0, depth * 0.5 + 0.006)
368 .with_scale(text_scale, text_scale, 1.0),
369 );
370 let _ = universe.attach(cap_root, text_root);
371
372 let wrap_at = wrap_at_for_button_middle_width(middle_w, text_scale);
373 let (cols, rows) = measure_word_wrapped_block(text, wrap_at);
374 let cols_f = cols.max(1) as f32;
375 let rows_f = rows.max(1) as f32;
376
377 // Center the text block around (0,0) in glyph-local units.
378 // X is left-to-right (0..cols-1), Y is down (0, -1, -2...).
379 let x_center = -0.5 * (cols_f - 1.0);
380 let y_center = 0.5 * (rows_f - 1.0);
381 let text_offset = universe
382 .world
383 .add_component(TransformComponent::new().with_position(x_center, y_center, 0.0));
384 let _ = universe.attach(text_root, text_offset);
385
386 let text_id = universe
387 .world
388 .add_component(TextComponent::with_word_wrap(text, wrap_at));
389 let _ = universe.attach(text_offset, text_id);
390
391 let cutout = universe
392 .world
393 .add_component(TransparentCutoutComponent::new());
394 let _ = universe.attach(text_id, cutout);
395
396 let filtering = universe
397 .world
398 .add_component(TextureFilteringComponent::nearest_magnification());
399 let _ = universe.attach(text_id, filtering);
400
401 universe.add(button_root);
402
403 // Attach text color *after* text has been built/initialized.
404 // We intentionally initialize the ColorComponent before attachment so that its `init()` will
405 // NOT re-run on attach; this exercises the TextSystem ParentChanged refresh behavior.
406 let text_color = universe
407 .world
408 .add_component(ColorComponent::rgba(0.0, 0.0, 0.0, 1.0));
409 universe.add(text_color);
410 let _ = universe.attach(text_id, text_color);
411
412 // Stash state for the signal handler.
413 let state = ButtonState {
414 pressed: false,
415 cap_root,
416 cap_raised_z,
417 cap_pressed_z,
418 frame_color,
419 face_color,
420 text_id,
421 text_color,
422 frame_color_up: color,
423 face_color_up: background_color,
424 text_color_up: [0.0, 0.0, 0.0, 1.0],
425 frame_color_down: darken(color, 0.75),
426 face_color_down: darken(background_color, 0.75),
427 text_color_down: [1.0, 1.0, 1.0, 1.0],
428 };
429
430 BUTTONS
431 .get_or_init(|| Mutex::new(HashMap::new()))
432 .lock()
433 .expect("button state lock")
434 .insert(button_root, state);
435
436 button_root
437}Sourcepub fn with_word_wrap_tokens<T, I>(
text: impl Into<String>,
wrap_at: usize,
tokens: I,
) -> Self
pub fn with_word_wrap_tokens<T, I>( text: impl Into<String>, wrap_at: usize, tokens: I, ) -> Self
Word-wrap (prefer wrapping at whitespace / tokens) aiming for wrap_at characters.
tokens are additional “wrap-allowed-after” sequences (e.g. “::”, “.”, “,”).
Whitespace tokens (space + tab) are always included.
Examples found in repository?
55 fn spawn_labeled_mesh(
56 universe: &mut engine::Universe,
57 x: f32,
58 y: f32,
59 label: &str,
60 mesh: engine::graphics::primitives::CpuMeshHandle,
61 scale: [f32; 3],
62 color: [f32; 4],
63 ) {
64 use engine::ecs::component::{
65 ActionComponent, AnimationComponent, AnimationState, ColorComponent, EmissiveComponent,
66 KeyframeComponent, RenderableComponent, TextComponent, TransformComponent,
67 };
68 use engine::graphics::primitives::{MaterialHandle, Renderable};
69
70 // Mesh.
71 let root = universe.world.add_component(
72 TransformComponent::new()
73 .with_position(x, y, 0.0)
74 .with_scale(scale[0], scale[1], scale[2]),
75 );
76
77 // Spin each shape around its own +Y axis using AnimationComponent + keyframes.
78 // We fill [0, 2) beats densely so it looks smooth.
79 let anim = universe
80 .world
81 .add_component(AnimationComponent::new().with_state(AnimationState::Looping));
82 let _ = universe.attach(root, anim);
83
84 let steps: usize = 64;
85 for i in 0..steps {
86 let beat = (i as f64) * (2.0 / (steps as f64));
87 let kf = universe.world.add_component(KeyframeComponent::new(beat));
88 let _ = universe.attach(anim, kf);
89
90 // Full turn over 2 beats.
91 let angle = (std::f64::consts::TAU * (beat / 2.0)) as f32;
92 let rotation = utils::math::quat_from_axis_angle([0.0, 1.0, 0.0], angle);
93
94 let action_cid = universe.world.add_component(ActionComponent::new(
95 engine::ecs::IntentValue::UpdateTransform {
96 component_ids: vec![root],
97 translation: [x, y, 0.0],
98 rotation_quat_xyzw: rotation,
99 scale,
100 },
101 ));
102 let _ = universe.attach(kf, action_cid);
103 }
104
105 let renderable = universe
106 .world
107 .add_component(RenderableComponent::new(Renderable::new(
108 mesh,
109 MaterialHandle::TOON_MESH,
110 )));
111 let color_c = universe
112 .world
113 .add_component(ColorComponent::rgba(color[0], color[1], color[2], color[3]));
114 let emissive = universe.world.add_component(EmissiveComponent::on());
115
116 let _ = universe.attach(root, renderable);
117 let _ = universe.attach(renderable, color_c);
118 let _ = universe.attach(renderable, emissive);
119
120 universe.add(root);
121
122 // Label (separate transform so we can scale text independently).
123 let text_root = universe.world.add_component(
124 TransformComponent::new()
125 .with_position(x, y + 0.75, 0.05)
126 .with_scale(0.09, 0.09, 1.0),
127 );
128 let text = universe
129 .world
130 .add_component(TextComponent::with_word_wrap_tokens(
131 label,
132 LABEL_WRAP_AT,
133 ["::", "(", ")", ",", "."],
134 ));
135 let text_color = universe
136 .world
137 .add_component(ColorComponent::rgba(1.0, 1.0, 1.0, 1.0));
138 let text_emissive = universe.world.add_component(EmissiveComponent::on());
139 let _ = universe.attach(text_root, text);
140 let _ = universe.attach(text, text_color);
141 let _ = universe.attach(text, text_emissive);
142 universe.add(text_root);
143 }pub fn with_font_size(self, font_size: f32) -> Self
pub fn set_font_size(&mut self, font_size: f32)
pub fn set_effective_font_size(&mut self, font_size: f32)
Trait Implementations§
Source§impl Clone for TextComponent
impl Clone for TextComponent
Source§fn clone(&self) -> TextComponent
fn clone(&self) -> TextComponent
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Component for TextComponent
impl Component for TextComponent
Source§fn name(&self) -> &'static str
fn name(&self) -> &'static str
fn set_id(&mut self, component: ComponentId)
fn as_any(&self) -> &dyn Any
fn as_any_mut(&mut self) -> &mut dyn Any
Source§fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId)
Source§fn to_mms_ast(&self, _world: &World) -> ComponentExpression
fn to_mms_ast(&self, _world: &World) -> ComponentExpression
Source§fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
fn cleanup(&mut self, _emit: &mut dyn SignalEmitter, _component: ComponentId)
Auto Trait Implementations§
impl Freeze for TextComponent
impl RefUnwindSafe for TextComponent
impl Send for TextComponent
impl Sync for TextComponent
impl Unpin for TextComponent
impl UnsafeUnpin for TextComponent
impl UnwindSafe for TextComponent
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.