返回 CodeWhale
pet_widget.rs
根目录 / crates / tui / src / tui / ambient_life / pet_widget.rs
1 //! The shared braille renderer, promoted from the portable pet study's TUI.
2 //! It owns no clock, telemetry, or motion. The habitat owns placement and ink.
3
4 use super::pet_sim::{PetSim, PetState, braille};
5 use ratatui::{
6 buffer::Buffer,
7 layout::{Alignment, Rect},
8 style::{Color, Style},
9 widgets::{Paragraph, Widget, Wrap},
10 };
11 use unicode_width::UnicodeWidthStr;
12
13 pub struct PetWidget<'a> {
14 pub sim: &'a PetSim,
15 pub state: &'a PetState,
16 }
17
18 impl Widget for PetWidget<'_> {
19 fn render(self, area: Rect, buf: &mut Buffer) {
20 let frame = self.sim.frame;
21 let label = format!(
22 "{} · {}{}",
23 frame.channel,
24 frame.arch,
25 if frame.hollow { " · unobserved" } else { "" }
26 );
27 // The creature and its non-colour cue are one unit. A narrow surface
28 // withholds both instead of silently dropping uncertainty or the gait.
29 if area.height < 4 || usize::from(area.width) < label.chars().count() {
30 return;
31 }
32 let tank = Rect {
33 height: area.height - 1,
34 ..area
35 };
36 let grid = braille(
37 self.sim,
38 tank.width as usize,
39 tank.height as usize,
40 self.state,
41 );
42 let fg = Color::Rgb(
43 (frame.r * frame.alpha).clamp(0.0, 255.0) as u8,
44 (frame.g * frame.alpha).clamp(0.0, 255.0) as u8,
45 (frame.b * frame.alpha).clamp(0.0, 255.0) as u8,
46 );
47 render_grid(area, buf, &grid, &label, Style::default().fg(fg));
48 }
49 }
50
51 /// Shared paint path for the Rust cameo and the embedded world's live raster.
52 /// A tiny viewport keeps the complete text cue before spending cells on dots.
53 pub(crate) fn render_grid(area: Rect, buf: &mut Buffer, grid: &[u8], label: &str, style: Style) {
54 if area.width == 0 || area.height == 0 {
55 return;
56 }
57 if label.width() > usize::from(area.width) || area.height < 4 {
58 Paragraph::new(label)
59 .style(style)
60 .alignment(Alignment::Center)
61 .wrap(Wrap { trim: false })
62 .render(area, buf);
63 return;
64 }
65 for y in 0..area.height - 1 {
66 for x in 0..area.width {
67 let bits = grid
68 .get(usize::from(y) * usize::from(area.width) + usize::from(x))
69 .copied()
70 .unwrap_or(0);
71 if bits != 0
72 && let Some(cell) = buf.cell_mut((area.x + x, area.y + y))
73 {
74 let glyph = char::from_u32(0x2800 + u32::from(bits)).expect("braille");
75 cell.set_symbol(&glyph.to_string()).set_style(style);
76 }
77 }
78 }
79 let x = area.x + (area.width - label.width() as u16) / 2;
80 buf.set_stringn(x, area.bottom() - 1, label, usize::from(area.width), style);
81 }
82
82 lines RUST