1use std::cell::Cell;
5use std::fmt;
6use std::rc::Rc;
7
8use ravenrs_core::{Color, Point, Quad, Rect, Scene};
9use ravenrs_layout::{Align, Axis, LayoutStyle};
10use ravenrs_style::Semantic;
11
12use crate::View;
13use crate::context::{BuildCx, NodeId, Paint};
14use crate::scroll_scene::ScrollRange;
15
16type Picture = Rc<dyn Fn(f32, f32, f64) -> Color>;
17
18#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
35pub struct QuadField {
36 columns: u16,
37 rows: u16,
38 picture: Picture,
39 height: f32,
40 range: ScrollRange,
41}
42
43impl fmt::Debug for QuadField {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 f.debug_struct("QuadField")
46 .field("columns", &self.columns)
47 .field("rows", &self.rows)
48 .finish_non_exhaustive()
49 }
50}
51
52#[cfg_attr(feature = "live", ravenrs_live::expose)]
53impl QuadField {
54 pub fn new(columns: u16, rows: u16, picture: impl Fn(f32, f32, f64) -> Color + 'static) -> Self {
57 Self {
58 columns: columns.max(1),
59 rows: rows.max(1),
60 picture: Rc::new(picture),
61 height: 600.0,
62 range: ScrollRange::Scene,
63 }
64 }
65
66 pub fn height(mut self, height: f32) -> Self {
68 self.height = height;
69 self
70 }
71
72 pub fn range(mut self, range: ScrollRange) -> Self {
75 self.range = range;
76 self
77 }
78}
79
80impl View for QuadField {
81 fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
82 let theme = *cx.theme();
83 let accent = theme.color(Semantic::Accent);
84 let glow = theme.color(Semantic::FocusRing);
85 let pointer = cx.use_state(|| Rc::new(Cell::new(Point::new(-1e4, -1e4)))).get();
86 let progress = Rc::new(Cell::new(0.0_f32));
87 let shown = progress.clone();
88 let (columns, rows, picture) = (self.columns, self.rows, self.picture.clone());
89 let at = pointer.clone();
90 let paint = Paint::Live(Rc::new(move |frame: Rect, time: f64, scene: &mut Scene| {
91 let e = smooth(shown.get());
92 draw(scene, frame, time, e, at.get(), columns, rows, &picture, accent, glow);
93 }));
94 let node = cx.container(
95 LayoutStyle {
96 height: Some(self.height),
97 align_self: Some(Align::Stretch),
98 ..LayoutStyle::stack(Axis::Vertical)
99 },
100 &[],
101 paint,
102 );
103 cx.scroll_progress(node, self.range, progress);
104 cx.on_pointer_move(
105 node,
106 Rc::new(move |p, frame| pointer.set(Point::new(p.x - frame.x(), p.y - frame.y()))),
107 );
108 cx.clip(node);
109 node
110 }
111}
112
113fn smooth(t: f32) -> f32 {
114 let t = t.clamp(0.0, 1.0);
115 t * t * (3.0 - 2.0 * t)
116}
117
118#[allow(clippy::too_many_arguments)]
120fn draw(
121 scene: &mut Scene,
122 frame: Rect,
123 time: f64,
124 e: f32,
125 pointer: Point,
126 columns: u16,
127 rows: u16,
128 picture: &Picture,
129 accent: Color,
130 glow: Color,
131) {
132 let (cw, ch) = (frame.width(), frame.height());
133 if cw <= 0.0 || ch <= 0.0 {
134 return;
135 }
136 let t = time as f32;
137 let (c, r) = (f32::from(columns), f32::from(rows));
138 let w = (cw * 0.9).min(1180.0) / 2.0;
139 let s = 2.3 - 1.3 * e;
140 let hh = w * r / c;
141 let a = 1.22 * (1.0 - e);
142 let (ca, sa) = (a.cos(), a.sin());
143 let d = 900.0;
144 let base = if cw < ch { 0.86 } else { 0.7 };
146 let center = Point::new(cw * 0.5, ch * (base - (base - 0.5) * e));
147 let amp = 30.0 * (1.0 - e);
148 let cell = 2.0 * w / c;
149 let sweep = (t * 0.22) % 1.8 - 0.4;
150 for j in 0..rows {
151 for i in 0..columns {
152 let u = (f32::from(i) + 0.5) / c;
153 let v = (f32::from(j) + 0.5) / r;
154 let x = (u * 2.0 - 1.0) * w * s;
155 let py = (v * 2.0 - 1.0) * hh * s;
156 let h = amp
157 * ((x * 0.006 + t * 1.1).sin()
158 + 0.6 * (py * 0.01 - t * 1.5).sin()
159 + 0.4 * ((x + py) * 0.004 + t * 0.6).sin());
160 let dz = d + py * sa + h * ca;
161 if dz < 60.0 {
162 continue;
163 }
164 let k = d / dz;
165 let mut p = Point::new(center.x + x * k, center.y + (py * ca - h * sa) * k);
166 let (dx, dy) = (p.x - pointer.x, p.y - pointer.y);
167 let near = (-(dx * dx + dy * dy) / 9000.0).exp();
168 p.y -= near * 46.0 * (1.0 - e);
169 if p.x < -20.0 || p.x > cw + 20.0 || p.y < -20.0 || p.y > ch + 20.0 {
170 continue;
171 }
172 let fl = ((h / amp.max(0.001) + 1.4) / 2.8).clamp(0.0, 1.0);
173 let depth = (k * 0.9).clamp(0.0, 1.0);
174 let floor = accent.lerp(glow, 0.45 * fl);
175 let floor_alpha = ((0.15 + 0.85 * fl) * depth + near * 0.6).min(1.0);
176 let mut shown = picture(u, v, time);
177 let shine = (1.0 - (u - sweep - v * 0.3).abs() * 9.0).max(0.0) * 0.25 + near * 0.3;
178 shown = shown.lerp(Color::WHITE, shine * e);
179 let color = floor.with_alpha(floor_alpha).lerp(shown, e);
180 let size = (cell * s * k * (0.2 + 0.66 * e)).max(1.0);
181 let rect = Rect::new(frame.x() + p.x - size / 2.0, frame.y() + p.y - size / 2.0, size, size);
182 scene.push_quad(Quad::new(rect, color));
183 }
184 }
185}