GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/container/content_bg.rs (13.4K)
1 //! Narrow-trait `ContentBg` (Phase 5n) — a standalone gradient-grid page background. Despite
2 //! the name it owns no children; its GraphController impl is a stub except the grid-geometry
3 //! setters (hosts configure the grid through the controller interface). Never hittable; the
4 //! background color itself is drawn by hosts reading `color()` (the geometry here is only the
5 //! cell/gap gradient grid, exactly the legacy `extra_quads`).
6
7 use crate::colors;
8 use crate::scene::layout::Rect;
9 use crate::scene::paint::PaintCtx;
10 use crate::widget::{Adapted, GraphController, GraphNode, Input, Layout, Paint};
11
12 pub struct ContentBg {
13 show_network_grid: bool,
14 grid_size_x: f32,
15 grid_size_y: f32,
16 grid_origin_x: f32,
17 grid_origin_y: f32,
18 skipped_row_h: f32,
19 skipped_col_w: f32,
20 }
21
22 impl ContentBg {
23 pub fn new() -> Adapted<ContentBg> {
24 Adapted::new(ContentBg { show_network_grid: false, grid_size_x: 150.0, grid_size_y: 75.0, grid_origin_x: 0.0, grid_origin_y: 0.0, skipped_row_h: 37.5, skipped_col_w: 37.5 })
25 }
26 }
27
28 impl Layout for ContentBg {}
29
30 impl Input for ContentBg {
31 /// Never hittable (legacy hit_test returned false unconditionally).
32 fn hit(&self, _rect: Rect, _x: f32, _y: f32) -> bool {
33 false
34 }
35
36 }
37
38 impl Paint for ContentBg {
39 fn color(&self) -> [f32; 4] {
40 if self.show_network_grid {
41 [0.0, 0.0, 0.0, 0.0]
42 } else {
43 colors::CONTENT_BG
44 }
45 }
46
47 fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
48 for (qx, qy, qw, qh, qc) in self.grid_quads(rect) {
49 ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
50 }
51 }
52 }
53
54 impl ContentBg {
55 /// The gradient grid geometry (legacy `extra_quads`), against `rect`.
56 fn grid_quads(&self, rect: Rect) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
57 let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
58 if !self.show_network_grid || self.grid_size_x <= 0.0 || self.grid_size_y <= 0.0 {
59 return vec![];
60 }
61 let mut quads = Vec::new();
62 let grid_color = [0.0, 0.0, 0.0, 0.0];
63 let max_alpha = colors::CONTENT_BG[3]; // Peak opacity in the middle of gradient cells matches non-gradient cells
64 let steps = 20; // Silky-smooth gradient transition
65
66 let step_y = self.grid_size_y + self.skipped_row_h;
67 let step_x = self.grid_size_x + self.skipped_col_w;
68
69 if step_y >= 4.0 && step_x >= 4.0 {
70 let ry_start = ((y - self.grid_origin_y) / step_y).floor() as i32 - 1;
71 let ry_end = ((y + h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
72 let ry_start = ry_start.max(-100_000);
73 let ry_end = ry_end.min(100_000);
74
75 let cx_start = ((x - self.grid_origin_x) / step_x).floor() as i32 - 1;
76 let cx_end = ((x + w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
77 let cx_start = cx_start.max(-100_000);
78 let cx_end = cx_end.min(100_000);
79
80 // Draw individual cell backgrounds to avoid stacking with gradients
81 for ry in ry_start..=ry_end {
82 let y1 = self.grid_origin_y + (ry as f32) * step_y;
83 let draw_start_y = y1.max(y);
84 let draw_end_y = (y1 + self.grid_size_y).min(y + h);
85 if draw_start_y < draw_end_y {
86 for cx in cx_start..=cx_end {
87 let x1 = self.grid_origin_x + (cx as f32) * step_x;
88 let draw_start_x = x1.max(x);
89 let draw_end_x = (x1 + self.grid_size_x).min(x + w);
90 if draw_start_x < draw_end_x {
91 quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, colors::CONTENT_BG));
92 }
93 }
94 }
95 }
96 }
97
98 // Draw interstitial row gradients (horizontal bands fading to 0 alpha at left and right sides)
99 if self.skipped_row_h > 0.0 {
100 let step_y = self.grid_size_y + self.skipped_row_h;
101 let step_x = self.grid_size_x + self.skipped_col_w;
102 if step_y >= 4.0 && step_x >= 4.0 {
103 let k_start = ((y - self.grid_origin_y) / step_y).floor() as i32 - 1;
104 let k_end = ((y + h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
105 let k_start = k_start.max(-100_000);
106 let k_end = k_end.min(100_000);
107
108 let cx_start = ((x - self.grid_origin_x) / step_x).floor() as i32 - 1;
109 let cx_end = ((x + w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
110 let cx_start = cx_start.max(-100_000);
111 let cx_end = cx_end.min(100_000);
112
113 for k in k_start..=k_end {
114 let y1 = self.grid_origin_y + (k as f32) * step_y;
115 let y2 = y1 + self.grid_size_y;
116 if y1 >= y + h {
117 continue;
118 }
119 let draw_start_y = y2.max(y);
120 let draw_end_y = (y2 + self.skipped_row_h).min(y + h);
121 if draw_start_y >= draw_end_y {
122 continue;
123 }
124
125 for cx in cx_start..=cx_end {
126 let x1 = self.grid_origin_x + (cx as f32) * step_x;
127 let x_mid = x1 + self.grid_size_x / 2.0;
128 let w_total = self.grid_size_x;
129 let sub_w = w_total / steps as f32;
130
131 for i in 0..steps {
132 let sx_start = x1 + i as f32 * sub_w;
133 let sx_end = sx_start + sub_w;
134 let draw_start_x = sx_start.max(x);
135 let draw_end_x = sx_end.min(x + w);
136 if draw_start_x < draw_end_x {
137 let sx_mid = (sx_start + sx_end) / 2.0;
138 let dist = (sx_mid - x_mid).abs();
139 let d = (dist / (w_total / 2.0)).min(1.0);
140
141 // Fade the cell background color from max_alpha in the middle to transparent at the edges
142 let alpha = max_alpha * (1.0 - d);
143 if alpha > 0.001 {
144 quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
145 }
146 }
147 }
148 }
149 }
150 }
151 }
152
153 // Draw interstitial column gradients (vertical bands fading to 0 alpha at top and bottom)
154 if self.skipped_col_w > 0.0 {
155 let step_y = self.grid_size_y + self.skipped_row_h;
156 let step_x = self.grid_size_x + self.skipped_col_w;
157 if step_y >= 4.0 && step_x >= 4.0 {
158 let k_start = ((x - self.grid_origin_x) / step_x).floor() as i32 - 1;
159 let k_end = ((x + w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
160 let k_start = k_start.max(-100_000);
161 let k_end = k_end.min(100_000);
162
163 let ry_start = ((y - self.grid_origin_y) / step_y).floor() as i32 - 1;
164 let ry_end = ((y + h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
165 let ry_start = ry_start.max(-100_000);
166 let ry_end = ry_end.min(100_000);
167
168 for k in k_start..=k_end {
169 let x1 = self.grid_origin_x + (k as f32) * step_x;
170 let x2 = x1 + self.grid_size_x;
171 if x1 >= x + w {
172 continue;
173 }
174 let draw_start_x = x2.max(x);
175 let draw_end_x = (x2 + self.skipped_col_w).min(x + w);
176 if draw_start_x >= draw_end_x {
177 continue;
178 }
179
180 for ry in ry_start..=ry_end {
181 let y1 = self.grid_origin_y + (ry as f32) * step_y;
182 let y_mid = y1 + self.grid_size_y / 2.0;
183 let h_total = self.grid_size_y;
184 let sub_h = h_total / steps as f32;
185
186 for i in 0..steps {
187 let sy_start = y1 + i as f32 * sub_h;
188 let sy_end = sy_start + sub_h;
189 let draw_start_y = sy_start.max(y);
190 let draw_end_y = sy_end.min(y + h);
191 if draw_start_y < draw_end_y {
192 let sy_mid = (sy_start + sy_end) / 2.0;
193 let dist = (sy_mid - y_mid).abs();
194 let d = (dist / (h_total / 2.0)).min(1.0);
195
196 // Fade the cell background color from max_alpha in the middle to transparent at the edges
197 let alpha = max_alpha * (1.0 - d);
198 if alpha > 0.001 {
199 quads.push((draw_start_x, draw_start_y, draw_end_x - draw_start_x, draw_end_y - draw_start_y, [colors::CONTENT_BG[0], colors::CONTENT_BG[1], colors::CONTENT_BG[2], alpha]));
200 }
201 }
202 }
203 }
204 }
205 }
206 }
207
208 // Draw the grid borders
209 let step_y = self.grid_size_y + self.skipped_row_h;
210 if step_y >= 4.0 {
211 let k_start = ((y - self.grid_origin_y) / step_y).floor() as i32 - 1;
212 let k_end = ((y + h - self.grid_origin_y) / step_y).ceil() as i32 + 1;
213 let k_start = k_start.max(-100_000);
214 let k_end = k_end.min(100_000);
215 for k in k_start..=k_end {
216 let y1 = self.grid_origin_y + (k as f32) * step_y;
217 let y2 = y1 + self.grid_size_y;
218 if y1 >= y + h {
219 continue;
220 }
221 if y1 >= y {
222 quads.push((x, y1, w, 1.0, grid_color));
223 }
224 if y2 >= y && y2 < y + h {
225 quads.push((x, y2, w, 1.0, grid_color));
226 }
227 }
228 }
229
230 let step_x = self.grid_size_x + self.skipped_col_w;
231 if step_x >= 4.0 {
232 let k_start = ((x - self.grid_origin_x) / step_x).floor() as i32 - 1;
233 let k_end = ((x + w - self.grid_origin_x) / step_x).ceil() as i32 + 1;
234 let k_start = k_start.max(-100_000);
235 let k_end = k_end.min(100_000);
236 for k in k_start..=k_end {
237 let x1 = self.grid_origin_x + (k as f32) * step_x;
238 let x2 = x1 + self.grid_size_x;
239 if x1 >= x + w {
240 continue;
241 }
242 if x1 >= x {
243 quads.push((x1, y, 1.0, h, grid_color));
244 }
245 if x2 >= x && x2 < x + w {
246 quads.push((x2, y, 1.0, h, grid_color));
247 }
248 }
249 }
250 quads
251 }
252 }
253
254 impl GraphController for ContentBg {
255 fn set_nodes(&mut self, _nodes: &[GraphNode]) {}
256 fn get_nodes(&self) -> Vec<GraphNode> { Vec::new() }
257 fn selected_node(&self) -> Option<usize> { None }
258 fn set_selected_node(&mut self, _idx: Option<usize>) {}
259 fn double_clicked_node(&self) -> Option<usize> { None }
260 fn clear_double_clicked_node(&mut self) {}
261 fn set_grid_snap_enabled(&mut self, _enabled: bool) {}
262 fn take_node_geom_toggle(&mut self) -> Option<(usize, bool)> { None }
263 fn set_grid_snap(&mut self, _gx: f32, _gy: f32) {}
264 /// This background keeps its cell-and-gap gradient; a pitch lands as a
265 /// cell of pitch-minus-gap, so the bands still add up to the pitch.
266 fn set_grid_pitch(&mut self, px: f32, py: f32) {
267 self.grid_size_x = (px - self.skipped_col_w).max(0.0);
268 self.grid_size_y = (py - self.skipped_row_h).max(0.0);
269 }
270 /// No nodes here; the cell-and-gap gradient has no body to size.
271 fn set_node_size(&mut self, _w: f32, _h: f32) {}
272 fn set_grid_sizes(&mut self, gx: f32, gy: f32) { self.grid_size_x = gx; self.grid_size_y = gy; }
273 fn set_skipped_sizes(&mut self, row_h: f32, col_w: f32) { self.skipped_row_h = row_h; self.skipped_col_w = col_w; }
274 fn set_grid_origin(&mut self, ox: f32, oy: f32) { self.grid_origin_x = ox; self.grid_origin_y = oy; }
275 fn grid_origin(&self) -> (f32, f32) { (self.grid_origin_x, self.grid_origin_y) }
276 fn set_show_network_grid(&mut self, show: bool) { self.show_network_grid = show; }
277 /// The same gradient grid its own `paint` draws, for a host walking the quads itself.
278 fn paint_grid(&self, rect: Rect, pc: &mut PaintCtx) {
279 for (qx, qy, qw, qh, qc) in self.grid_quads(rect) {
280 pc.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
281 }
282 }
283 fn take_pending_connection(&mut self) -> Option<(String, String)> { None }
284 fn cancel_connecting(&mut self) {}
285 fn is_node_rect(&self, _qx: f32, _qy: f32, _qw: f32, _qh: f32) -> bool { false }
286 fn node_at(&self, _px: f32, _py: f32) -> Option<usize> { None }
287 fn cell_corner_radius(&self) -> f32 { 0.0 }
288 fn geometry_quads_tagged(&self, _rect: crate::scene::layout::Rect) -> Vec<crate::widget::TaggedQuad> { Vec::new() }
289 fn drop_target_cell_rect(&self) -> Option<(f32, f32, f32, f32)> { None }
290 }