pub struct SpriteDrawableBuilder { /* private fields */ }Implementations§
Source§impl SpriteDrawableBuilder
impl SpriteDrawableBuilder
pub fn new() -> Self
Sourcepub fn position(self, position: impl Into<Point<i32>>) -> Self
pub fn position(self, position: impl Into<Point<i32>>) -> Self
Examples found in repository?
examples/swarm.rs (line 224)
186pub fn main() -> Result<(), AppError> {
187 init_terminal!();
188 init_logger("./organic_swarm.log")?;
189
190 // creating the tick manager for syncing the waves with the main swarm
191 let (_manager, manager_handle) = TickManager::new(tick_manager_rs::Speed::Fps(50));
192 let main_member = tick_manager_rs::TickMember::new(manager_handle.clone(), 1);
193 let bg_member = tick_manager_rs::TickMember::new(manager_handle, 6);
194
195 let (cols, rows) = size()?;
196 let renderer = Renderer::<DefaultScreenBuffer<CrosstermCellDrawer>, Buffered>::create_renderer(
197 (cols, rows),
198 );
199 let mut r = start_renderer(renderer);
200 r.set_update_interval(16)?;
201
202 let screen = r.create_screen(AreaRect::FullScreen, 5)?;
203
204 let (tx, video_drawable) = make_videostream_drawable((0, 0), 1)?;
205 let video_id = DrawObjectBuilder::default()
206 .layer(1)
207 .screen(screen)
208 .drawable(video_drawable)
209 .shader(Grayscale)
210 .add_lifetime(ObjectLifetime::ExplicitRemove)
211 .build_and_register(&mut r)?;
212 spawn_soft_wave(
213 tx,
214 RenderHandle::<Buffered>::clone(&r),
215 video_id,
216 (cols, rows),
217 bg_member,
218 );
219
220 // Optional: tiny sprite clock in the corner (if you have a .ascv around, otherwise harmless).
221 if let Ok(sprite) = r.register_sprite_from_source("./assets/debugging/test_video.ascv") {
222 let spr_draw = SpriteDrawableBuilder::default()
223 .sprite_id(sprite)
224 .position((0, 50))
225 .animation_type(AnimationInfo::Video {
226 loop_type: VideoLoopType::Loop,
227 speed: VideoSpeed::Fps(4),
228 start_frame: FrameIdent::FirstFrame,
229 end_frame: FrameIdent::LastFrame,
230 })
231 .build()?;
232 let _spr_id = DrawObjectBuilder::default()
233 .layer(2)
234 .screen(screen)
235 .drawable(spr_draw)
236 .add_lifetime(ObjectLifetime::ExplicitRemove)
237 .build_and_register(&mut r)?;
238 }
239 let rect_id: DrawObjectKey = DrawObjectBuilder::default()
240 .layer(100)
241 .screen(screen)
242 .rect_drawable(|r| {
243 r.border_style(
244 ltrender::terminal_buffer::standard_drawables::rect_drawable::BorderStyle::Custom {
245 top: TerminalChar::from_char('T'),
246 bottom: TerminalChar::from_char('B'),
247 left: TerminalChar::from_char('L'),
248 right: TerminalChar::from_char('R'),
249 },
250 )
251 .border_thickness(2)
252 .fill_style(' ')
253 .rect(Rect::from_coords(20, 20, 40, 40))
254 })?
255 .add_lifetime(ObjectLifetime::ExplicitRemove)
256 .build_and_register(&mut r)?;
257
258 // HUD text (toggle with 'h').
259 let mut show_hud = true;
260 let hud_drawable = Box::new(text_drawable::TextDrawable {
261 area: Rect::from_coords(1, 1, 50, 10),
262 lines: vec![text_drawable::LineInfo {
263 text: String::from(
264 "organic swarm — drag mouse, left: attract, right: repel, scroll: strength, n: reseed, h: HUD, q: quit",
265 ),
266 spans: vec![],
267 alignment: text_drawable::TextAlignment::Left,
268 default_style: text_drawable::TextStyle {
269 foreground: Some(Color::White),
270 bold: Some(true),
271 ..Default::default()
272 },
273 }],
274 wrapping: true,
275 scroll_y: 0,
276 });
277 let hud_id = DrawObjectBuilder::default()
278 .layer(9)
279 .screen(screen)
280 .drawable(hud_drawable)
281 .add_lifetime(ObjectLifetime::ExplicitRemove)
282 .build_and_register(&mut r)?;
283
284 // Seed agents.
285 let mut agents = seeded_swarm(&mut r, screen, 140, cols, rows);
286
287 // Input and timing state.
288 let start = Instant::now();
289 let mut mouse = MouseState {
290 x: (cols / 2) as i32,
291 y: (rows / 2) as i32,
292 left: false,
293 right: false,
294 strength: 18.0,
295 };
296 let mut running = true;
297
298 let mut render_start;
299 let mut fps_vec: Vec<f64> = Vec::new();
300 let mut time_since_last_fps_log = Instant::now();
301
302 while running {
303 render_start = Instant::now();
304 // Input ----------------------------------------------------
305 if poll(Duration::from_millis(0))? {
306 match read()? {
307 Event::Key(k) => {
308 match k.code {
309 KeyCode::Esc | KeyCode::Char('q') => running = false,
310 KeyCode::Char('n') => {
311 // Reseed: drop old agents visually and create new ones.
312 for a in &agents {
313 r.explicit_remove_drawable(&a.id)?;
314 }
315 agents = seeded_swarm(&mut r, screen, 140, cols, rows);
316 }
317 KeyCode::Char('h') => {
318 show_hud = !show_hud;
319 if show_hud {
320 r.render_drawable(hud_id)?;
321 } else {
322 r.explicit_remove_drawable(&hud_id)?;
323 }
324 }
325 _ => {}
326 }
327 }
328 Event::Mouse(me) => match me.kind {
329 MouseEventKind::Moved | MouseEventKind::Drag(_) => {
330 mouse.x = me.column as i32;
331 mouse.y = me.row as i32;
332 }
333 MouseEventKind::Down(btn) => {
334 use crossterm::event::MouseButton;
335 match btn {
336 MouseButton::Left => mouse.left = true,
337 MouseButton::Right => mouse.right = true,
338 MouseButton::Middle => {
339 mouse.strength = 12.0;
340 }
341 }
342 }
343 MouseEventKind::Up(btn) => {
344 use crossterm::event::MouseButton;
345 match btn {
346 MouseButton::Left => mouse.left = false,
347 MouseButton::Right => mouse.right = false,
348 MouseButton::Middle => {}
349 }
350 }
351 MouseEventKind::ScrollDown => {
352 mouse.strength = (mouse.strength - 2.0).max(4.0);
353 }
354 MouseEventKind::ScrollUp => {
355 mouse.strength = (mouse.strength + 2.0).min(40.0);
356 }
357 _ => {}
358 },
359 Event::Resize(c, rws) => {
360 r.handle_resize((c, rws))?;
361 }
362 _ => {}
363 }
364 }
365
366 // Simulation ----------------------------------------------
367 let (cw, ch) = (cols as f64, rows as f64);
368 let t = start.elapsed().as_secs_f64();
369
370 for a in &mut agents {
371 let fx = ((a.pos.1 * 0.05 + t * 0.3).sin() * 0.8) + (t * 0.1 + a.wobble).cos() * 0.2;
372 let fy = ((a.pos.0 * 0.05 - t * 0.25).cos() * 0.8) + (t * 0.13).sin() * 0.2;
373
374 let mut steer_x = fx;
375 let mut steer_y = fy;
376
377 // Mouse field
378 let dx = mouse.x as f64 - a.pos.0;
379 let dy = mouse.y as f64 - a.pos.1;
380 let d2 = (dx * dx + dy * dy).max(0.0001);
381 let inv = 1.0 / d2.sqrt();
382 let toward = (dx * inv, dy * inv);
383
384 if mouse.left {
385 // attract
386 steer_x += toward.0 * (mouse.strength * inv);
387 steer_y += toward.1 * (mouse.strength * inv);
388 }
389 if mouse.right {
390 // repel
391 steer_x -= toward.0 * (mouse.strength * inv);
392 steer_y -= toward.1 * (mouse.strength * inv);
393 }
394
395 // Convert steer into angle change.
396 let desired = steer_y.atan2(steer_x);
397 let mut diff = desired - a.dir;
398 while diff > std::f64::consts::PI {
399 diff -= std::f64::consts::TAU;
400 }
401 while diff < -std::f64::consts::PI {
402 diff += std::f64::consts::TAU;
403 }
404
405 a.dir += diff * 0.15 + (t * a.wobble).sin() * 0.02;
406 a.speed = lerp(a.speed, 1.2 + (t * 0.7 + a.wobble).sin().abs(), 0.05);
407
408 // Integrate position with gentle easing near mouse to make it feel alive.
409 let step = a.speed;
410 a.pos.0 = wrap(a.pos.0 + a.dir.cos() * step, cw);
411 a.pos.1 = wrap(a.pos.1 + a.dir.sin() * step, ch);
412
413 // Push to renderer
414 let pts = triangle_points(a.pos.0, a.pos.1, a.dir, a.size);
415 r.replace_drawable_points(a.id, pts)?;
416 r.render_drawable(a.id)?;
417 }
418
419 r.render_drawable(rect_id)?;
420
421 let frame_preperation_duration = render_start.elapsed();
422
423 if show_hud {
424 let fps = 1.0 / frame_preperation_duration.as_secs_f64();
425 fps_vec.push(fps);
426
427 if time_since_last_fps_log.elapsed() > Duration::from_secs(5) {
428 let count = fps_vec.len();
429
430 if count > 0 {
431 let sum: f64 = fps_vec.iter().sum();
432 let avg_fps = sum / count as f64;
433 info!("Avg FPS over the last 5s: {:.2} ", avg_fps);
434 }
435 fps_vec.clear();
436 time_since_last_fps_log = Instant::now();
437 }
438
439 let txt = format!(
440 "time spent rendering frame: ~{fps:.1} | agents: {} | force: {:.1} | mouse: ({}, {}) L:{} R:{}",
441 agents.len(),
442 mouse.strength,
443 mouse.x,
444 mouse.y,
445 mouse.left as u8,
446 mouse.right as u8
447 );
448
449 let hud = text_drawable::TextDrawable {
450 area: Rect::from_coords(1, 1, 100, 6),
451 lines: vec![text_drawable::LineInfo {
452 text: txt,
453 spans: vec![],
454 alignment: text_drawable::TextAlignment::Left,
455 default_style: text_drawable::TextStyle {
456 foreground: Some(Color::White),
457 bold: Some(true),
458 ..Default::default()
459 },
460 }],
461 wrapping: true,
462 scroll_y: 0,
463 };
464 r.replace_drawable(hud_id, Box::new(hud))?;
465 r.render_drawable(hud_id)?;
466 }
467 main_member.wait_for_tick();
468 r.render_frame()?;
469 }
470
471 // cleanup
472 for a in &agents {
473 r.explicit_remove_drawable(&a.id)?;
474 }
475 r.explicit_remove_drawable(&video_id)?;
476
477 ltrender::restore_terminal()?;
478 Ok(())
479}Sourcepub fn sprite_id(self, id: SpriteId) -> Self
pub fn sprite_id(self, id: SpriteId) -> Self
Examples found in repository?
examples/swarm.rs (line 223)
186pub fn main() -> Result<(), AppError> {
187 init_terminal!();
188 init_logger("./organic_swarm.log")?;
189
190 // creating the tick manager for syncing the waves with the main swarm
191 let (_manager, manager_handle) = TickManager::new(tick_manager_rs::Speed::Fps(50));
192 let main_member = tick_manager_rs::TickMember::new(manager_handle.clone(), 1);
193 let bg_member = tick_manager_rs::TickMember::new(manager_handle, 6);
194
195 let (cols, rows) = size()?;
196 let renderer = Renderer::<DefaultScreenBuffer<CrosstermCellDrawer>, Buffered>::create_renderer(
197 (cols, rows),
198 );
199 let mut r = start_renderer(renderer);
200 r.set_update_interval(16)?;
201
202 let screen = r.create_screen(AreaRect::FullScreen, 5)?;
203
204 let (tx, video_drawable) = make_videostream_drawable((0, 0), 1)?;
205 let video_id = DrawObjectBuilder::default()
206 .layer(1)
207 .screen(screen)
208 .drawable(video_drawable)
209 .shader(Grayscale)
210 .add_lifetime(ObjectLifetime::ExplicitRemove)
211 .build_and_register(&mut r)?;
212 spawn_soft_wave(
213 tx,
214 RenderHandle::<Buffered>::clone(&r),
215 video_id,
216 (cols, rows),
217 bg_member,
218 );
219
220 // Optional: tiny sprite clock in the corner (if you have a .ascv around, otherwise harmless).
221 if let Ok(sprite) = r.register_sprite_from_source("./assets/debugging/test_video.ascv") {
222 let spr_draw = SpriteDrawableBuilder::default()
223 .sprite_id(sprite)
224 .position((0, 50))
225 .animation_type(AnimationInfo::Video {
226 loop_type: VideoLoopType::Loop,
227 speed: VideoSpeed::Fps(4),
228 start_frame: FrameIdent::FirstFrame,
229 end_frame: FrameIdent::LastFrame,
230 })
231 .build()?;
232 let _spr_id = DrawObjectBuilder::default()
233 .layer(2)
234 .screen(screen)
235 .drawable(spr_draw)
236 .add_lifetime(ObjectLifetime::ExplicitRemove)
237 .build_and_register(&mut r)?;
238 }
239 let rect_id: DrawObjectKey = DrawObjectBuilder::default()
240 .layer(100)
241 .screen(screen)
242 .rect_drawable(|r| {
243 r.border_style(
244 ltrender::terminal_buffer::standard_drawables::rect_drawable::BorderStyle::Custom {
245 top: TerminalChar::from_char('T'),
246 bottom: TerminalChar::from_char('B'),
247 left: TerminalChar::from_char('L'),
248 right: TerminalChar::from_char('R'),
249 },
250 )
251 .border_thickness(2)
252 .fill_style(' ')
253 .rect(Rect::from_coords(20, 20, 40, 40))
254 })?
255 .add_lifetime(ObjectLifetime::ExplicitRemove)
256 .build_and_register(&mut r)?;
257
258 // HUD text (toggle with 'h').
259 let mut show_hud = true;
260 let hud_drawable = Box::new(text_drawable::TextDrawable {
261 area: Rect::from_coords(1, 1, 50, 10),
262 lines: vec![text_drawable::LineInfo {
263 text: String::from(
264 "organic swarm — drag mouse, left: attract, right: repel, scroll: strength, n: reseed, h: HUD, q: quit",
265 ),
266 spans: vec![],
267 alignment: text_drawable::TextAlignment::Left,
268 default_style: text_drawable::TextStyle {
269 foreground: Some(Color::White),
270 bold: Some(true),
271 ..Default::default()
272 },
273 }],
274 wrapping: true,
275 scroll_y: 0,
276 });
277 let hud_id = DrawObjectBuilder::default()
278 .layer(9)
279 .screen(screen)
280 .drawable(hud_drawable)
281 .add_lifetime(ObjectLifetime::ExplicitRemove)
282 .build_and_register(&mut r)?;
283
284 // Seed agents.
285 let mut agents = seeded_swarm(&mut r, screen, 140, cols, rows);
286
287 // Input and timing state.
288 let start = Instant::now();
289 let mut mouse = MouseState {
290 x: (cols / 2) as i32,
291 y: (rows / 2) as i32,
292 left: false,
293 right: false,
294 strength: 18.0,
295 };
296 let mut running = true;
297
298 let mut render_start;
299 let mut fps_vec: Vec<f64> = Vec::new();
300 let mut time_since_last_fps_log = Instant::now();
301
302 while running {
303 render_start = Instant::now();
304 // Input ----------------------------------------------------
305 if poll(Duration::from_millis(0))? {
306 match read()? {
307 Event::Key(k) => {
308 match k.code {
309 KeyCode::Esc | KeyCode::Char('q') => running = false,
310 KeyCode::Char('n') => {
311 // Reseed: drop old agents visually and create new ones.
312 for a in &agents {
313 r.explicit_remove_drawable(&a.id)?;
314 }
315 agents = seeded_swarm(&mut r, screen, 140, cols, rows);
316 }
317 KeyCode::Char('h') => {
318 show_hud = !show_hud;
319 if show_hud {
320 r.render_drawable(hud_id)?;
321 } else {
322 r.explicit_remove_drawable(&hud_id)?;
323 }
324 }
325 _ => {}
326 }
327 }
328 Event::Mouse(me) => match me.kind {
329 MouseEventKind::Moved | MouseEventKind::Drag(_) => {
330 mouse.x = me.column as i32;
331 mouse.y = me.row as i32;
332 }
333 MouseEventKind::Down(btn) => {
334 use crossterm::event::MouseButton;
335 match btn {
336 MouseButton::Left => mouse.left = true,
337 MouseButton::Right => mouse.right = true,
338 MouseButton::Middle => {
339 mouse.strength = 12.0;
340 }
341 }
342 }
343 MouseEventKind::Up(btn) => {
344 use crossterm::event::MouseButton;
345 match btn {
346 MouseButton::Left => mouse.left = false,
347 MouseButton::Right => mouse.right = false,
348 MouseButton::Middle => {}
349 }
350 }
351 MouseEventKind::ScrollDown => {
352 mouse.strength = (mouse.strength - 2.0).max(4.0);
353 }
354 MouseEventKind::ScrollUp => {
355 mouse.strength = (mouse.strength + 2.0).min(40.0);
356 }
357 _ => {}
358 },
359 Event::Resize(c, rws) => {
360 r.handle_resize((c, rws))?;
361 }
362 _ => {}
363 }
364 }
365
366 // Simulation ----------------------------------------------
367 let (cw, ch) = (cols as f64, rows as f64);
368 let t = start.elapsed().as_secs_f64();
369
370 for a in &mut agents {
371 let fx = ((a.pos.1 * 0.05 + t * 0.3).sin() * 0.8) + (t * 0.1 + a.wobble).cos() * 0.2;
372 let fy = ((a.pos.0 * 0.05 - t * 0.25).cos() * 0.8) + (t * 0.13).sin() * 0.2;
373
374 let mut steer_x = fx;
375 let mut steer_y = fy;
376
377 // Mouse field
378 let dx = mouse.x as f64 - a.pos.0;
379 let dy = mouse.y as f64 - a.pos.1;
380 let d2 = (dx * dx + dy * dy).max(0.0001);
381 let inv = 1.0 / d2.sqrt();
382 let toward = (dx * inv, dy * inv);
383
384 if mouse.left {
385 // attract
386 steer_x += toward.0 * (mouse.strength * inv);
387 steer_y += toward.1 * (mouse.strength * inv);
388 }
389 if mouse.right {
390 // repel
391 steer_x -= toward.0 * (mouse.strength * inv);
392 steer_y -= toward.1 * (mouse.strength * inv);
393 }
394
395 // Convert steer into angle change.
396 let desired = steer_y.atan2(steer_x);
397 let mut diff = desired - a.dir;
398 while diff > std::f64::consts::PI {
399 diff -= std::f64::consts::TAU;
400 }
401 while diff < -std::f64::consts::PI {
402 diff += std::f64::consts::TAU;
403 }
404
405 a.dir += diff * 0.15 + (t * a.wobble).sin() * 0.02;
406 a.speed = lerp(a.speed, 1.2 + (t * 0.7 + a.wobble).sin().abs(), 0.05);
407
408 // Integrate position with gentle easing near mouse to make it feel alive.
409 let step = a.speed;
410 a.pos.0 = wrap(a.pos.0 + a.dir.cos() * step, cw);
411 a.pos.1 = wrap(a.pos.1 + a.dir.sin() * step, ch);
412
413 // Push to renderer
414 let pts = triangle_points(a.pos.0, a.pos.1, a.dir, a.size);
415 r.replace_drawable_points(a.id, pts)?;
416 r.render_drawable(a.id)?;
417 }
418
419 r.render_drawable(rect_id)?;
420
421 let frame_preperation_duration = render_start.elapsed();
422
423 if show_hud {
424 let fps = 1.0 / frame_preperation_duration.as_secs_f64();
425 fps_vec.push(fps);
426
427 if time_since_last_fps_log.elapsed() > Duration::from_secs(5) {
428 let count = fps_vec.len();
429
430 if count > 0 {
431 let sum: f64 = fps_vec.iter().sum();
432 let avg_fps = sum / count as f64;
433 info!("Avg FPS over the last 5s: {:.2} ", avg_fps);
434 }
435 fps_vec.clear();
436 time_since_last_fps_log = Instant::now();
437 }
438
439 let txt = format!(
440 "time spent rendering frame: ~{fps:.1} | agents: {} | force: {:.1} | mouse: ({}, {}) L:{} R:{}",
441 agents.len(),
442 mouse.strength,
443 mouse.x,
444 mouse.y,
445 mouse.left as u8,
446 mouse.right as u8
447 );
448
449 let hud = text_drawable::TextDrawable {
450 area: Rect::from_coords(1, 1, 100, 6),
451 lines: vec![text_drawable::LineInfo {
452 text: txt,
453 spans: vec![],
454 alignment: text_drawable::TextAlignment::Left,
455 default_style: text_drawable::TextStyle {
456 foreground: Some(Color::White),
457 bold: Some(true),
458 ..Default::default()
459 },
460 }],
461 wrapping: true,
462 scroll_y: 0,
463 };
464 r.replace_drawable(hud_id, Box::new(hud))?;
465 r.render_drawable(hud_id)?;
466 }
467 main_member.wait_for_tick();
468 r.render_frame()?;
469 }
470
471 // cleanup
472 for a in &agents {
473 r.explicit_remove_drawable(&a.id)?;
474 }
475 r.explicit_remove_drawable(&video_id)?;
476
477 ltrender::restore_terminal()?;
478 Ok(())
479}Sourcepub fn animation_type(self, anim: AnimationInfo) -> Self
pub fn animation_type(self, anim: AnimationInfo) -> Self
Examples found in repository?
examples/swarm.rs (lines 225-230)
186pub fn main() -> Result<(), AppError> {
187 init_terminal!();
188 init_logger("./organic_swarm.log")?;
189
190 // creating the tick manager for syncing the waves with the main swarm
191 let (_manager, manager_handle) = TickManager::new(tick_manager_rs::Speed::Fps(50));
192 let main_member = tick_manager_rs::TickMember::new(manager_handle.clone(), 1);
193 let bg_member = tick_manager_rs::TickMember::new(manager_handle, 6);
194
195 let (cols, rows) = size()?;
196 let renderer = Renderer::<DefaultScreenBuffer<CrosstermCellDrawer>, Buffered>::create_renderer(
197 (cols, rows),
198 );
199 let mut r = start_renderer(renderer);
200 r.set_update_interval(16)?;
201
202 let screen = r.create_screen(AreaRect::FullScreen, 5)?;
203
204 let (tx, video_drawable) = make_videostream_drawable((0, 0), 1)?;
205 let video_id = DrawObjectBuilder::default()
206 .layer(1)
207 .screen(screen)
208 .drawable(video_drawable)
209 .shader(Grayscale)
210 .add_lifetime(ObjectLifetime::ExplicitRemove)
211 .build_and_register(&mut r)?;
212 spawn_soft_wave(
213 tx,
214 RenderHandle::<Buffered>::clone(&r),
215 video_id,
216 (cols, rows),
217 bg_member,
218 );
219
220 // Optional: tiny sprite clock in the corner (if you have a .ascv around, otherwise harmless).
221 if let Ok(sprite) = r.register_sprite_from_source("./assets/debugging/test_video.ascv") {
222 let spr_draw = SpriteDrawableBuilder::default()
223 .sprite_id(sprite)
224 .position((0, 50))
225 .animation_type(AnimationInfo::Video {
226 loop_type: VideoLoopType::Loop,
227 speed: VideoSpeed::Fps(4),
228 start_frame: FrameIdent::FirstFrame,
229 end_frame: FrameIdent::LastFrame,
230 })
231 .build()?;
232 let _spr_id = DrawObjectBuilder::default()
233 .layer(2)
234 .screen(screen)
235 .drawable(spr_draw)
236 .add_lifetime(ObjectLifetime::ExplicitRemove)
237 .build_and_register(&mut r)?;
238 }
239 let rect_id: DrawObjectKey = DrawObjectBuilder::default()
240 .layer(100)
241 .screen(screen)
242 .rect_drawable(|r| {
243 r.border_style(
244 ltrender::terminal_buffer::standard_drawables::rect_drawable::BorderStyle::Custom {
245 top: TerminalChar::from_char('T'),
246 bottom: TerminalChar::from_char('B'),
247 left: TerminalChar::from_char('L'),
248 right: TerminalChar::from_char('R'),
249 },
250 )
251 .border_thickness(2)
252 .fill_style(' ')
253 .rect(Rect::from_coords(20, 20, 40, 40))
254 })?
255 .add_lifetime(ObjectLifetime::ExplicitRemove)
256 .build_and_register(&mut r)?;
257
258 // HUD text (toggle with 'h').
259 let mut show_hud = true;
260 let hud_drawable = Box::new(text_drawable::TextDrawable {
261 area: Rect::from_coords(1, 1, 50, 10),
262 lines: vec![text_drawable::LineInfo {
263 text: String::from(
264 "organic swarm — drag mouse, left: attract, right: repel, scroll: strength, n: reseed, h: HUD, q: quit",
265 ),
266 spans: vec![],
267 alignment: text_drawable::TextAlignment::Left,
268 default_style: text_drawable::TextStyle {
269 foreground: Some(Color::White),
270 bold: Some(true),
271 ..Default::default()
272 },
273 }],
274 wrapping: true,
275 scroll_y: 0,
276 });
277 let hud_id = DrawObjectBuilder::default()
278 .layer(9)
279 .screen(screen)
280 .drawable(hud_drawable)
281 .add_lifetime(ObjectLifetime::ExplicitRemove)
282 .build_and_register(&mut r)?;
283
284 // Seed agents.
285 let mut agents = seeded_swarm(&mut r, screen, 140, cols, rows);
286
287 // Input and timing state.
288 let start = Instant::now();
289 let mut mouse = MouseState {
290 x: (cols / 2) as i32,
291 y: (rows / 2) as i32,
292 left: false,
293 right: false,
294 strength: 18.0,
295 };
296 let mut running = true;
297
298 let mut render_start;
299 let mut fps_vec: Vec<f64> = Vec::new();
300 let mut time_since_last_fps_log = Instant::now();
301
302 while running {
303 render_start = Instant::now();
304 // Input ----------------------------------------------------
305 if poll(Duration::from_millis(0))? {
306 match read()? {
307 Event::Key(k) => {
308 match k.code {
309 KeyCode::Esc | KeyCode::Char('q') => running = false,
310 KeyCode::Char('n') => {
311 // Reseed: drop old agents visually and create new ones.
312 for a in &agents {
313 r.explicit_remove_drawable(&a.id)?;
314 }
315 agents = seeded_swarm(&mut r, screen, 140, cols, rows);
316 }
317 KeyCode::Char('h') => {
318 show_hud = !show_hud;
319 if show_hud {
320 r.render_drawable(hud_id)?;
321 } else {
322 r.explicit_remove_drawable(&hud_id)?;
323 }
324 }
325 _ => {}
326 }
327 }
328 Event::Mouse(me) => match me.kind {
329 MouseEventKind::Moved | MouseEventKind::Drag(_) => {
330 mouse.x = me.column as i32;
331 mouse.y = me.row as i32;
332 }
333 MouseEventKind::Down(btn) => {
334 use crossterm::event::MouseButton;
335 match btn {
336 MouseButton::Left => mouse.left = true,
337 MouseButton::Right => mouse.right = true,
338 MouseButton::Middle => {
339 mouse.strength = 12.0;
340 }
341 }
342 }
343 MouseEventKind::Up(btn) => {
344 use crossterm::event::MouseButton;
345 match btn {
346 MouseButton::Left => mouse.left = false,
347 MouseButton::Right => mouse.right = false,
348 MouseButton::Middle => {}
349 }
350 }
351 MouseEventKind::ScrollDown => {
352 mouse.strength = (mouse.strength - 2.0).max(4.0);
353 }
354 MouseEventKind::ScrollUp => {
355 mouse.strength = (mouse.strength + 2.0).min(40.0);
356 }
357 _ => {}
358 },
359 Event::Resize(c, rws) => {
360 r.handle_resize((c, rws))?;
361 }
362 _ => {}
363 }
364 }
365
366 // Simulation ----------------------------------------------
367 let (cw, ch) = (cols as f64, rows as f64);
368 let t = start.elapsed().as_secs_f64();
369
370 for a in &mut agents {
371 let fx = ((a.pos.1 * 0.05 + t * 0.3).sin() * 0.8) + (t * 0.1 + a.wobble).cos() * 0.2;
372 let fy = ((a.pos.0 * 0.05 - t * 0.25).cos() * 0.8) + (t * 0.13).sin() * 0.2;
373
374 let mut steer_x = fx;
375 let mut steer_y = fy;
376
377 // Mouse field
378 let dx = mouse.x as f64 - a.pos.0;
379 let dy = mouse.y as f64 - a.pos.1;
380 let d2 = (dx * dx + dy * dy).max(0.0001);
381 let inv = 1.0 / d2.sqrt();
382 let toward = (dx * inv, dy * inv);
383
384 if mouse.left {
385 // attract
386 steer_x += toward.0 * (mouse.strength * inv);
387 steer_y += toward.1 * (mouse.strength * inv);
388 }
389 if mouse.right {
390 // repel
391 steer_x -= toward.0 * (mouse.strength * inv);
392 steer_y -= toward.1 * (mouse.strength * inv);
393 }
394
395 // Convert steer into angle change.
396 let desired = steer_y.atan2(steer_x);
397 let mut diff = desired - a.dir;
398 while diff > std::f64::consts::PI {
399 diff -= std::f64::consts::TAU;
400 }
401 while diff < -std::f64::consts::PI {
402 diff += std::f64::consts::TAU;
403 }
404
405 a.dir += diff * 0.15 + (t * a.wobble).sin() * 0.02;
406 a.speed = lerp(a.speed, 1.2 + (t * 0.7 + a.wobble).sin().abs(), 0.05);
407
408 // Integrate position with gentle easing near mouse to make it feel alive.
409 let step = a.speed;
410 a.pos.0 = wrap(a.pos.0 + a.dir.cos() * step, cw);
411 a.pos.1 = wrap(a.pos.1 + a.dir.sin() * step, ch);
412
413 // Push to renderer
414 let pts = triangle_points(a.pos.0, a.pos.1, a.dir, a.size);
415 r.replace_drawable_points(a.id, pts)?;
416 r.render_drawable(a.id)?;
417 }
418
419 r.render_drawable(rect_id)?;
420
421 let frame_preperation_duration = render_start.elapsed();
422
423 if show_hud {
424 let fps = 1.0 / frame_preperation_duration.as_secs_f64();
425 fps_vec.push(fps);
426
427 if time_since_last_fps_log.elapsed() > Duration::from_secs(5) {
428 let count = fps_vec.len();
429
430 if count > 0 {
431 let sum: f64 = fps_vec.iter().sum();
432 let avg_fps = sum / count as f64;
433 info!("Avg FPS over the last 5s: {:.2} ", avg_fps);
434 }
435 fps_vec.clear();
436 time_since_last_fps_log = Instant::now();
437 }
438
439 let txt = format!(
440 "time spent rendering frame: ~{fps:.1} | agents: {} | force: {:.1} | mouse: ({}, {}) L:{} R:{}",
441 agents.len(),
442 mouse.strength,
443 mouse.x,
444 mouse.y,
445 mouse.left as u8,
446 mouse.right as u8
447 );
448
449 let hud = text_drawable::TextDrawable {
450 area: Rect::from_coords(1, 1, 100, 6),
451 lines: vec![text_drawable::LineInfo {
452 text: txt,
453 spans: vec![],
454 alignment: text_drawable::TextAlignment::Left,
455 default_style: text_drawable::TextStyle {
456 foreground: Some(Color::White),
457 bold: Some(true),
458 ..Default::default()
459 },
460 }],
461 wrapping: true,
462 scroll_y: 0,
463 };
464 r.replace_drawable(hud_id, Box::new(hud))?;
465 r.render_drawable(hud_id)?;
466 }
467 main_member.wait_for_tick();
468 r.render_frame()?;
469 }
470
471 // cleanup
472 for a in &agents {
473 r.explicit_remove_drawable(&a.id)?;
474 }
475 r.explicit_remove_drawable(&video_id)?;
476
477 ltrender::restore_terminal()?;
478 Ok(())
479}Sourcepub fn build(self) -> Result<Box<dyn Drawable>, DrawObjectBuilderError>
pub fn build(self) -> Result<Box<dyn Drawable>, DrawObjectBuilderError>
Examples found in repository?
examples/swarm.rs (line 231)
186pub fn main() -> Result<(), AppError> {
187 init_terminal!();
188 init_logger("./organic_swarm.log")?;
189
190 // creating the tick manager for syncing the waves with the main swarm
191 let (_manager, manager_handle) = TickManager::new(tick_manager_rs::Speed::Fps(50));
192 let main_member = tick_manager_rs::TickMember::new(manager_handle.clone(), 1);
193 let bg_member = tick_manager_rs::TickMember::new(manager_handle, 6);
194
195 let (cols, rows) = size()?;
196 let renderer = Renderer::<DefaultScreenBuffer<CrosstermCellDrawer>, Buffered>::create_renderer(
197 (cols, rows),
198 );
199 let mut r = start_renderer(renderer);
200 r.set_update_interval(16)?;
201
202 let screen = r.create_screen(AreaRect::FullScreen, 5)?;
203
204 let (tx, video_drawable) = make_videostream_drawable((0, 0), 1)?;
205 let video_id = DrawObjectBuilder::default()
206 .layer(1)
207 .screen(screen)
208 .drawable(video_drawable)
209 .shader(Grayscale)
210 .add_lifetime(ObjectLifetime::ExplicitRemove)
211 .build_and_register(&mut r)?;
212 spawn_soft_wave(
213 tx,
214 RenderHandle::<Buffered>::clone(&r),
215 video_id,
216 (cols, rows),
217 bg_member,
218 );
219
220 // Optional: tiny sprite clock in the corner (if you have a .ascv around, otherwise harmless).
221 if let Ok(sprite) = r.register_sprite_from_source("./assets/debugging/test_video.ascv") {
222 let spr_draw = SpriteDrawableBuilder::default()
223 .sprite_id(sprite)
224 .position((0, 50))
225 .animation_type(AnimationInfo::Video {
226 loop_type: VideoLoopType::Loop,
227 speed: VideoSpeed::Fps(4),
228 start_frame: FrameIdent::FirstFrame,
229 end_frame: FrameIdent::LastFrame,
230 })
231 .build()?;
232 let _spr_id = DrawObjectBuilder::default()
233 .layer(2)
234 .screen(screen)
235 .drawable(spr_draw)
236 .add_lifetime(ObjectLifetime::ExplicitRemove)
237 .build_and_register(&mut r)?;
238 }
239 let rect_id: DrawObjectKey = DrawObjectBuilder::default()
240 .layer(100)
241 .screen(screen)
242 .rect_drawable(|r| {
243 r.border_style(
244 ltrender::terminal_buffer::standard_drawables::rect_drawable::BorderStyle::Custom {
245 top: TerminalChar::from_char('T'),
246 bottom: TerminalChar::from_char('B'),
247 left: TerminalChar::from_char('L'),
248 right: TerminalChar::from_char('R'),
249 },
250 )
251 .border_thickness(2)
252 .fill_style(' ')
253 .rect(Rect::from_coords(20, 20, 40, 40))
254 })?
255 .add_lifetime(ObjectLifetime::ExplicitRemove)
256 .build_and_register(&mut r)?;
257
258 // HUD text (toggle with 'h').
259 let mut show_hud = true;
260 let hud_drawable = Box::new(text_drawable::TextDrawable {
261 area: Rect::from_coords(1, 1, 50, 10),
262 lines: vec![text_drawable::LineInfo {
263 text: String::from(
264 "organic swarm — drag mouse, left: attract, right: repel, scroll: strength, n: reseed, h: HUD, q: quit",
265 ),
266 spans: vec![],
267 alignment: text_drawable::TextAlignment::Left,
268 default_style: text_drawable::TextStyle {
269 foreground: Some(Color::White),
270 bold: Some(true),
271 ..Default::default()
272 },
273 }],
274 wrapping: true,
275 scroll_y: 0,
276 });
277 let hud_id = DrawObjectBuilder::default()
278 .layer(9)
279 .screen(screen)
280 .drawable(hud_drawable)
281 .add_lifetime(ObjectLifetime::ExplicitRemove)
282 .build_and_register(&mut r)?;
283
284 // Seed agents.
285 let mut agents = seeded_swarm(&mut r, screen, 140, cols, rows);
286
287 // Input and timing state.
288 let start = Instant::now();
289 let mut mouse = MouseState {
290 x: (cols / 2) as i32,
291 y: (rows / 2) as i32,
292 left: false,
293 right: false,
294 strength: 18.0,
295 };
296 let mut running = true;
297
298 let mut render_start;
299 let mut fps_vec: Vec<f64> = Vec::new();
300 let mut time_since_last_fps_log = Instant::now();
301
302 while running {
303 render_start = Instant::now();
304 // Input ----------------------------------------------------
305 if poll(Duration::from_millis(0))? {
306 match read()? {
307 Event::Key(k) => {
308 match k.code {
309 KeyCode::Esc | KeyCode::Char('q') => running = false,
310 KeyCode::Char('n') => {
311 // Reseed: drop old agents visually and create new ones.
312 for a in &agents {
313 r.explicit_remove_drawable(&a.id)?;
314 }
315 agents = seeded_swarm(&mut r, screen, 140, cols, rows);
316 }
317 KeyCode::Char('h') => {
318 show_hud = !show_hud;
319 if show_hud {
320 r.render_drawable(hud_id)?;
321 } else {
322 r.explicit_remove_drawable(&hud_id)?;
323 }
324 }
325 _ => {}
326 }
327 }
328 Event::Mouse(me) => match me.kind {
329 MouseEventKind::Moved | MouseEventKind::Drag(_) => {
330 mouse.x = me.column as i32;
331 mouse.y = me.row as i32;
332 }
333 MouseEventKind::Down(btn) => {
334 use crossterm::event::MouseButton;
335 match btn {
336 MouseButton::Left => mouse.left = true,
337 MouseButton::Right => mouse.right = true,
338 MouseButton::Middle => {
339 mouse.strength = 12.0;
340 }
341 }
342 }
343 MouseEventKind::Up(btn) => {
344 use crossterm::event::MouseButton;
345 match btn {
346 MouseButton::Left => mouse.left = false,
347 MouseButton::Right => mouse.right = false,
348 MouseButton::Middle => {}
349 }
350 }
351 MouseEventKind::ScrollDown => {
352 mouse.strength = (mouse.strength - 2.0).max(4.0);
353 }
354 MouseEventKind::ScrollUp => {
355 mouse.strength = (mouse.strength + 2.0).min(40.0);
356 }
357 _ => {}
358 },
359 Event::Resize(c, rws) => {
360 r.handle_resize((c, rws))?;
361 }
362 _ => {}
363 }
364 }
365
366 // Simulation ----------------------------------------------
367 let (cw, ch) = (cols as f64, rows as f64);
368 let t = start.elapsed().as_secs_f64();
369
370 for a in &mut agents {
371 let fx = ((a.pos.1 * 0.05 + t * 0.3).sin() * 0.8) + (t * 0.1 + a.wobble).cos() * 0.2;
372 let fy = ((a.pos.0 * 0.05 - t * 0.25).cos() * 0.8) + (t * 0.13).sin() * 0.2;
373
374 let mut steer_x = fx;
375 let mut steer_y = fy;
376
377 // Mouse field
378 let dx = mouse.x as f64 - a.pos.0;
379 let dy = mouse.y as f64 - a.pos.1;
380 let d2 = (dx * dx + dy * dy).max(0.0001);
381 let inv = 1.0 / d2.sqrt();
382 let toward = (dx * inv, dy * inv);
383
384 if mouse.left {
385 // attract
386 steer_x += toward.0 * (mouse.strength * inv);
387 steer_y += toward.1 * (mouse.strength * inv);
388 }
389 if mouse.right {
390 // repel
391 steer_x -= toward.0 * (mouse.strength * inv);
392 steer_y -= toward.1 * (mouse.strength * inv);
393 }
394
395 // Convert steer into angle change.
396 let desired = steer_y.atan2(steer_x);
397 let mut diff = desired - a.dir;
398 while diff > std::f64::consts::PI {
399 diff -= std::f64::consts::TAU;
400 }
401 while diff < -std::f64::consts::PI {
402 diff += std::f64::consts::TAU;
403 }
404
405 a.dir += diff * 0.15 + (t * a.wobble).sin() * 0.02;
406 a.speed = lerp(a.speed, 1.2 + (t * 0.7 + a.wobble).sin().abs(), 0.05);
407
408 // Integrate position with gentle easing near mouse to make it feel alive.
409 let step = a.speed;
410 a.pos.0 = wrap(a.pos.0 + a.dir.cos() * step, cw);
411 a.pos.1 = wrap(a.pos.1 + a.dir.sin() * step, ch);
412
413 // Push to renderer
414 let pts = triangle_points(a.pos.0, a.pos.1, a.dir, a.size);
415 r.replace_drawable_points(a.id, pts)?;
416 r.render_drawable(a.id)?;
417 }
418
419 r.render_drawable(rect_id)?;
420
421 let frame_preperation_duration = render_start.elapsed();
422
423 if show_hud {
424 let fps = 1.0 / frame_preperation_duration.as_secs_f64();
425 fps_vec.push(fps);
426
427 if time_since_last_fps_log.elapsed() > Duration::from_secs(5) {
428 let count = fps_vec.len();
429
430 if count > 0 {
431 let sum: f64 = fps_vec.iter().sum();
432 let avg_fps = sum / count as f64;
433 info!("Avg FPS over the last 5s: {:.2} ", avg_fps);
434 }
435 fps_vec.clear();
436 time_since_last_fps_log = Instant::now();
437 }
438
439 let txt = format!(
440 "time spent rendering frame: ~{fps:.1} | agents: {} | force: {:.1} | mouse: ({}, {}) L:{} R:{}",
441 agents.len(),
442 mouse.strength,
443 mouse.x,
444 mouse.y,
445 mouse.left as u8,
446 mouse.right as u8
447 );
448
449 let hud = text_drawable::TextDrawable {
450 area: Rect::from_coords(1, 1, 100, 6),
451 lines: vec![text_drawable::LineInfo {
452 text: txt,
453 spans: vec![],
454 alignment: text_drawable::TextAlignment::Left,
455 default_style: text_drawable::TextStyle {
456 foreground: Some(Color::White),
457 bold: Some(true),
458 ..Default::default()
459 },
460 }],
461 wrapping: true,
462 scroll_y: 0,
463 };
464 r.replace_drawable(hud_id, Box::new(hud))?;
465 r.render_drawable(hud_id)?;
466 }
467 main_member.wait_for_tick();
468 r.render_frame()?;
469 }
470
471 // cleanup
472 for a in &agents {
473 r.explicit_remove_drawable(&a.id)?;
474 }
475 r.explicit_remove_drawable(&video_id)?;
476
477 ltrender::restore_terminal()?;
478 Ok(())
479}Trait Implementations§
Source§impl Default for SpriteDrawableBuilder
impl Default for SpriteDrawableBuilder
Source§fn default() -> SpriteDrawableBuilder
fn default() -> SpriteDrawableBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for SpriteDrawableBuilder
impl RefUnwindSafe for SpriteDrawableBuilder
impl Send for SpriteDrawableBuilder
impl Sync for SpriteDrawableBuilder
impl Unpin for SpriteDrawableBuilder
impl UnsafeUnpin for SpriteDrawableBuilder
impl UnwindSafe for SpriteDrawableBuilder
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
Mutably borrows from an owned value. Read more