返回 CodeWhale
guarded_legacy.rs
根目录 / crates / tui / src / tui / ocean / guarded_legacy.rs
1 use super::*;
2 use crate::tui::ambient_life::{frame_ocean_ramp, occupied_text_bounds};
3 use crate::tui::ocean;
4 use ratatui::{style::Modifier, text::Line};
5
6 pub(super) struct LegacyColumn(pub(super) OceanColumn);
7 impl LegacyColumn {
8 fn color_at_y(&self, y: u16) -> Color {
9 self.0.color_at_y(y)
10 }
11 pub fn paint_matching(self, area: Rect, buf: &mut Buffer, background: Color) {
12 let area = area.intersection(buf.area);
13 for y in area.top()..area.bottom() {
14 let row_bg = self.color_at_y(y);
15 for x in area.left()..area.right() {
16 let cell = &mut buf[(x, y)];
17 if cell.bg == background && !cell.modifier.contains(Modifier::REVERSED) {
18 cell.set_bg(row_bg);
19 }
20 }
21 }
22 }
23 }
24
25 pub(super) struct LegacyWater {
26 pub(super) ocean_column: Option<OceanColumn>,
27 pub(super) lines: Vec<Line<'static>>,
28 pub(super) background: Color,
29 pub(super) ocean_elapsed_ms: u128,
30 }
31 impl LegacyWater {
32 pub(super) fn paint(&self, area: Rect, buf: &mut Buffer) {
33 if let Some(column) = self.ocean_column {
34 // Cache per-row ocean colors; invalidate only on phase/size/breath.
35 let phase_tag = column.phase_tag();
36 let fingerprint = column.ramp_fingerprint();
37 let ramp = crate::tui::ambient_life::frame_ocean_ramp(
38 &column,
39 area.height,
40 area.y,
41 self.ocean_elapsed_ms,
42 phase_tag,
43 fingerprint,
44 );
45 for local_y in 0..area.height {
46 let protected = self
47 .lines
48 .get(usize::from(local_y))
49 .and_then(occupied_text_bounds);
50 let row_bg = ramp
51 .get(usize::from(local_y))
52 .copied()
53 .unwrap_or_else(|| column.color_at_y(area.y.saturating_add(local_y)));
54 for local_x in 0..area.width {
55 let is_protected = protected.is_some_and(|(start, end)| {
56 usize::from(local_x) >= start && usize::from(local_x) < end
57 });
58 let cell = &mut buf[(area.x + local_x, area.y + local_y)];
59 // Plain transcript text participates in the water column;
60 // explicit semantic surfaces (selection, code, warnings)
61 // retain their own background.
62 if !is_protected || cell.bg == self.background {
63 cell.set_bg(row_bg);
64 }
65 }
66 }
67 }
68 }
69 }
70
71 pub fn apply_caustic_shimmer(
72 area: Rect,
73 buf: &mut Buffer,
74 column: &OceanColumn,
75 elapsed_ms: u128,
76 animated: bool,
77 lines: &[Line<'static>],
78 ) {
79 if !animated || area.width < AMBIENT_MIN_WIDTH || area.height < AMBIENT_MIN_HEIGHT {
80 return;
81 }
82 // Sparse sampling: every 3rd column on every other row near the surface.
83 //
84 // The light stops where the composition starts. Sunlight raking across
85 // the rows a wordmark is sitting in is the same failure as a fish
86 // swimming through them, just quieter, and it costs nothing to measure:
87 // the surface band is clipped to the first row that carries text.
88 let ceiling = (0..area.height)
89 .find(|row| {
90 lines
91 .get(usize::from(*row))
92 .and_then(occupied_text_bounds)
93 .is_some()
94 })
95 .unwrap_or(area.height);
96 let band = (area.height / 3).max(2).min(ceiling);
97 for local_y in 0..band {
98 let protected = lines
99 .get(usize::from(local_y))
100 .and_then(occupied_text_bounds);
101 let ramp = frame_ocean_ramp(
102 column,
103 area.height,
104 area.y,
105 elapsed_ms,
106 column.phase_tag(),
107 column.ramp_fingerprint(),
108 );
109 let row_bg = ramp
110 .get(usize::from(local_y))
111 .copied()
112 .unwrap_or_else(|| column.color_at_y(area.y.saturating_add(local_y)));
113 for local_x in (0..area.width).step_by(3) {
114 if protected.is_some_and(|(start, end)| {
115 usize::from(local_x) >= start && usize::from(local_x) < end
116 }) {
117 continue;
118 }
119 let cell = &mut buf[(area.x + local_x, area.y + local_y)];
120 // Soften toward ambient ink without replacing semantic glyphs.
121 if cell.symbol() == " " || cell.symbol().is_empty() {
122 // Sunlight dissolves with depth instead of stopping: full
123 // amplitude at the surface easing to zero at the band's
124 // floor. The former hard cutoff at `band` drew a visible
125 // horizontal line across tall windows.
126 let depth_fade = 1.0 - f32::from(local_y) / f32::from(band.max(1));
127 let shimmer = ocean::scale_color(
128 row_bg,
129 caustic_brightness(elapsed_ms, local_x, local_y, depth_fade * depth_fade),
130 );
131 cell.set_bg(shimmer);
132 }
133 }
134 }
135 }
136
137 /// Continuous travelling caustic. The former `(elapsed / 80) % 12` mask
138 /// toggled cells fully on/off at 12.5 Hz; truecolor made that quantization look
139 /// like dropped frames. A narrow cosine crest preserves the same sparse light
140 /// band while cross-fading every sampled cell between frames.
141 fn caustic_brightness(elapsed_ms: u128, local_x: u16, local_y: u16, depth_fade: f32) -> f32 {
142 const CYCLE_MS: f64 = 960.0;
143 const SPATIAL_SLOTS: f64 = 4.0;
144 let time = (elapsed_ms % CYCLE_MS as u128) as f64 / CYCLE_MS;
145 // The sampled grid advances by three terminal columns. Four grid phases
146 // therefore preserve the old 12-column repeat instead of stretching the
147 // caustic topology while changing only its temporal interpolation.
148 let slot = (u32::from(local_x / 3) + u32::from(local_y)) % 4;
149 let phase = (time + f64::from(slot) / SPATIAL_SLOTS) * std::f64::consts::TAU;
150 let crest = ((phase.cos() + 1.0) * 0.5).powi(8);
151 1.0 + 0.08 * (crest as f32) * depth_fade.clamp(0.0, 1.0)
152 }
153
154 // End of exact legacy caustic/brightness source fragment.
155
155 lines RUST