Skip to main content

SpriteDrawableBuilder

Struct SpriteDrawableBuilder 

Source
pub struct SpriteDrawableBuilder { /* private fields */ }

Implementations§

Source§

impl SpriteDrawableBuilder

Source

pub fn new() -> Self

Source

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}
Source

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}
Source

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}
Source

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

Source§

fn default() -> SpriteDrawableBuilder

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.