git.lucas.co / cce-ui
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 }