file manager
git clone https://git.lucas.co/cce-files.git
src/pages/mod.rs (23.9K)
1 pub mod browse;
2 pub mod preview;
3 pub mod network;
4 pub mod space;
5
6 use cce_ui::widget::Handle;
7 use cce_ui::layout::RenderTarget;
8
9 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
10 pub enum Page {
11 Browse,
12 Network,
13 Space,
14 }
15
16 impl Page {
17 pub const ALL: [Page; 3] = [
18 Page::Browse,
19 Page::Network,
20 Page::Space,
21 ];
22
23 pub fn label(self) -> &'static str {
24 match self {
25 Page::Browse => "Browse",
26 Page::Network => "Network",
27 Page::Space => "Space",
28 }
29 }
30
31 /// The page's row in the breadcrumb's view menu. These name the
32 /// visualization rather than the page, so they are not [`Self::label`].
33 pub fn view_label(self) -> &'static str {
34 match self {
35 Page::Browse => "List",
36 Page::Network => "Graph",
37 Page::Space => "Space",
38 }
39 }
40 }
41
42 /// The breadcrumb's height on every page. Its segments are button plates, so
43 /// it is one button tall: the toolkit's control height (as the widget's own
44 /// `intrinsic_size` says), not a number of this crate's.
45 pub fn breadcrumb_h() -> f32 {
46 cce_ui::layout::button_height()
47 }
48
49 /// Lay out, render and carve a page's breadcrumb: the pane's full width along
50 /// its top edge, the same on every page, so switching views leaves it where it
51 /// was. Returns the top of the content under it, one plate gap below.
52 ///
53 /// Network and Space used to place theirs 4px in, 6px down and 16px narrower
54 /// than Browse, an inset left over from before the pane rect carried it, and
55 /// started their content 28px and 34px down against Browse's 24 + gap.
56 pub fn breadcrumb_header(
57 pc: &mut PageContent,
58 breadcrumb: Handle<cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>>,
59 cx: f32,
60 cy: f32,
61 cw: f32,
62 ctx: &mut cce_ui::context::UiContext,
63 ) -> f32 {
64 let h = breadcrumb_h();
65 cce_ui::layout::render_widget_h(pc, breadcrumb, cx, cy, cw, h, ctx);
66 let rect = cce_ui::scene::layout::Rect { x: cx, y: cy, width: cw, height: h };
67 breadcrumb_relief(pc, &ctx[breadcrumb], rect);
68 cy + h + cce_ui::layout::root_plate_gap()
69 }
70
71 /// Mirror the breadcrumb's relief into a flat-path [`PageContent`]: the
72 /// full-width recessed well, the ONE raised plate the segment run shares, and a
73 /// slanted seam engraved at each boundary between two segments.
74 ///
75 /// `render_widget` drops the relief prims `Breadcrumb::paint` emits, so every
76 /// page that shows a breadcrumb has to carve it app-side — this is that carve,
77 /// in one place, since all three pages want it identically.
78 ///
79 /// **Audited against `Breadcrumb::paint` and deliberately left page-side** —
80 /// it does NOT need the treatment the view dropdown's ring needed (carved into
81 /// `window_pc` after the pages had laid it out; the dropdown is gone since
82 /// 2026-10-06, its List/Graph/Space rows moved into this breadcrumb's context
83 /// menu), for two reasons that are easy to assume away:
84 ///
85 /// - *The depths already agree.* Each `relief_*`/`groove` helper derives depth
86 /// from the height it is handed, and all three here are handed what the
87 /// widget uses: the well from `rect.height`, the plate from `rh`, and the
88 /// seams from the run as host — the widget engraves the seams at the RUN's
89 /// depth, not the well's. Nothing is pre-expanded, so nothing drifts the way
90 /// the dropdown's ring did.
91 /// - *The rect is fresh.* Every caller builds it as a literal on the line after
92 /// laying the breadcrumb out, rather than reading it back off the widget, so
93 /// there is no previous-frame rect to pick up.
94 ///
95 /// Nor does the `window_pc`-vs-`pc` split matter here, though it did for the
96 /// dropdown's ring. `display_list` emits ALL of a `PageContent`'s rects
97 /// before ALL of its reliefs, so the call order within `pc` is irrelevant; only
98 /// a different PageContent could reorder these. The only quad the breadcrumb
99 /// puts under the carve is the hover tint, and that is inset to the seam's
100 /// furthest lean by construction, so it does not overlap the grooves. Moving
101 /// this carve to `window_pc` would buy nothing.
102 pub fn breadcrumb_relief(
103 pc: &mut PageContent,
104 breadcrumb: &cce_ui::widget::Adapted<cce_ui::widget::Breadcrumb>,
105 rect: cce_ui::scene::layout::Rect,
106 ) {
107 let r = cce_ui::layout::dropdown_corner_radius();
108 let Some(run) = breadcrumb.run_box(rect) else { return };
109 let (rx, ry, rw, rh) = run;
110 // The dropdown's flush inset plate on the segment run (mirroring
111 // Breadcrumb::paint's relief branch) — the full-rect recessed well and
112 // the raised run inside it are gone with the restyle.
113 //
114 // Face AND ring, through the `inset_plate` bridge, because the face is
115 // the dropdown's configured fill: `render_widget` offers that fill for a
116 // Dropdown through a per-type hook (layout.rs) but has no Breadcrumb arm,
117 // so a run carved here with no face would keep showing the window plate
118 // while the DE's dropdowns went opaque. It reads
119 // `dropdown_background_color` too, so they match under any config —
120 // transparent leaves the plate as the face.
121 let radius = r.min(rh * 0.5);
122 let depth = cce_ui::layout::bevel_width().min(rh * 0.2);
123 let raw = cce_ui::color::dropdown_background_color();
124 let face = if raw[3] > 0.001 {
125 let mut c = raw;
126 c[3] = 1.0;
127 c
128 } else {
129 [0.0; 4]
130 };
131 pc.inset_plate(face, rx, ry, rw, rh, radius, depth);
132 for (a, b) in breadcrumb.seams(rect) {
133 pc.groove(a, b, cce_ui::widget::Breadcrumb::SEAM_WIDTH, run);
134 }
135 }
136
137 pub const RELIEF_RECESSED: u8 = 0;
138 pub const RELIEF_RAISED: u8 = 1;
139 pub const RELIEF_INSET: u8 = 2;
140 /// A recessed well with pointer focus: renders as the tinted carve — the
141 /// wrapped accent glint REPLACING the relief lighting (the DE's one focus
142 /// language, same treatment as a focused plate's ring).
143 pub const RELIEF_RECESSED_FOCUS: u8 = 3;
144 /// [`RELIEF_INSET`] with keyboard focus: the flush plate's rim lit in the
145 /// highlight — the ring a focused control plate wears (`ControlPlate::with_tint`).
146 pub const RELIEF_INSET_FOCUS: u8 = 4;
147
148 /// A stroked shape a widget draws: a graph's wire (a line, or an arc at a
149 /// rounded bend) or its port (a disc). Coordinates as `PaintCtx` takes them;
150 /// an arc's `radius` is its OUTER edge.
151 #[derive(Debug, Clone, Copy, PartialEq)]
152 pub enum Stroke {
153 Line { x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, cap: cce_ui::scene::paint::Cap },
154 Arc { cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32 },
155 Disc { cx: f32, cy: f32, radius: f32 },
156 }
157
158 impl Stroke {
159 /// The shape's bounding box `(x, y, w, h)` — what culls it.
160 pub fn bounds(&self) -> (f32, f32, f32, f32) {
161 match *self {
162 Stroke::Line { x1, y1, x2, y2, thickness, .. } => {
163 let h = thickness * 0.5;
164 (x1.min(x2) - h, y1.min(y2) - h, (x1 - x2).abs() + thickness, (y1 - y2).abs() + thickness)
165 }
166 Stroke::Arc { cx, cy, radius, .. } | Stroke::Disc { cx, cy, radius } => {
167 (cx - radius, cy - radius, 2.0 * radius, 2.0 * radius)
168 }
169 }
170 }
171 }
172
173 pub struct PageContent {
174 pub rects: Vec<([f32; 4], f32, f32, f32, f32, f32, (bool, bool, bool, bool))>,
175 pub texts: Vec<(String, f32, f32, f32, [f32; 4], Option<String>, Option<[f32; 4]>)>,
176 pub buttons: Vec<(cce_ui::widget::Adapted<cce_ui::widget::Button>, crate::Message)>,
177 /// Relief steps for the control_relief styling — (x, y, w, h, radius, depth,
178 /// kind: [`RELIEF_RECESSED`]/[`RELIEF_RAISED`]/[`RELIEF_INSET`]). The flat rects
179 /// own the faces; these are the edges-only walls emitted over them (the
180 /// ParametersBg::reliefs idiom for flat-view hosts).
181 pub reliefs: Vec<(f32, f32, f32, f32, f32, f32, u8)>,
182 /// Engraved lines over the flat rects — (ax, ay, bx, by, width, depth) plus
183 /// the (x, y, w, h) of the surface being engraved, which the shading fades
184 /// out against. Unlike [`reliefs`] these are not axis-aligned: this is the
185 /// breadcrumb's slanted segment seams.
186 ///
187 /// [`reliefs`]: PageContent::reliefs
188 pub grooves: Vec<(f32, f32, f32, f32, f32, f32, f32, f32, f32, f32)>,
189 /// GPU-textured quads — (image id from `cce_ui::vk::upload_rgba`, x, y, w, h,
190 /// alpha). Drawn after the part's rects, so a fill emitted earlier is the floor
191 /// beneath the image and overlay parts still cover it.
192 pub images: Vec<(u32, f32, f32, f32, f32, f32)>,
193 /// cce-icons glyphs — (name, x, y, w, h, colour, clip `[l, t, r, b]`).
194 /// The colour is a text colour (raw sRGB, alpha = opacity), so a glyph and
195 /// the label beside it match; drawn through `PaintCtx::icon` in the part's
196 /// order, after its images. The clip cuts a glyph scrolled half under an
197 /// edge rather than squashing it, as text bounds cut a label.
198 pub icons: Vec<(String, f32, f32, f32, f32, [f32; 4], Option<[f32; 4]>)>,
199 /// Strokes, arcs and discs — (shape, colour, clip `[l, t, r, b]`): a
200 /// widget's wires and ports as `render_widget` hands them over
201 /// (`RenderTarget::line` / `arc` / `circle`). The graph page's wires.
202 pub strokes: Vec<(Stroke, [f32; 4], Option<[f32; 4]>)>,
203 /// Lit plates — (color, x, y, w, h, radius, depth). Unlike [`reliefs`], a plate
204 /// owns its FILL as well as its edge: one primitive carrying a rounded face and
205 /// the rolled, lit perimeter, shaded in a single lighting evaluation. That is
206 /// what the pane plates and the preview stub wear, so a floating surface
207 /// (the context menu) reads as the same material rather than as a flat chip
208 /// inside a drawn frame.
209 ///
210 /// [`reliefs`]: PageContent::reliefs
211 pub plates: Vec<([f32; 4], f32, f32, f32, f32, f32, f32)>,
212 }
213
214 impl PageContent {
215 pub fn new() -> Self {
216 Self {
217 rects: Vec::new(),
218 texts: Vec::new(),
219 buttons: Vec::new(),
220 reliefs: Vec::new(),
221 grooves: Vec::new(),
222 images: Vec::new(),
223 icons: Vec::new(),
224 strokes: Vec::new(),
225 plates: Vec::new(),
226 }
227 }
228
229 /// Move every part of `other` into this content.
230 ///
231 /// Field-complete by construction: this replaces four hand-listed runs of
232 /// `self.x.extend(other.x)` in `rebuild_layout`, which silently dropped any
233 /// vec nobody remembered to add a line for — `grooves` was invisible for
234 /// exactly that reason. The destructuring below turns a new field into a
235 /// compile error instead of a missing mark on screen.
236 pub fn absorb(&mut self, other: PageContent) {
237 let PageContent { rects, texts, buttons, reliefs, grooves, images, icons, strokes, plates } = other;
238 self.rects.extend(rects);
239 self.texts.extend(texts);
240 self.buttons.extend(buttons);
241 self.reliefs.extend(reliefs);
242 self.grooves.extend(grooves);
243 self.images.extend(images);
244 self.icons.extend(icons);
245 self.strokes.extend(strokes);
246 self.plates.extend(plates);
247 }
248
249 /// This content cut to `[l, t, r, b]`: fills intersected with it (a
250 /// fill wholly outside is dropped), and the bounds of every label and
251 /// glyph narrowed to it. For a widget that paints past the rect it was
252 /// given — the graph, panned — since a flat host has no clip stack.
253 /// Strokes are cut by their clip as glyphs are. Parts with no clip of
254 /// their own (buttons, reliefs, grooves, images,
255 /// plates) pass through as they are.
256 pub fn clipped_to(mut self, clip: [f32; 4]) -> Self {
257 let [l, t, r, b] = clip;
258 self.rects.retain_mut(|(_, x, y, w, h, _, _)| {
259 let (x0, y0) = (x.max(l), y.max(t));
260 let (x1, y1) = ((*x + *w).min(r), (*y + *h).min(b));
261 if x1 <= x0 || y1 <= y0 {
262 return false;
263 }
264 (*x, *y, *w, *h) = (x0, y0, x1 - x0, y1 - y0);
265 true
266 });
267 let narrow = |bounds: Option<[f32; 4]>| -> Option<[f32; 4]> {
268 Some(match bounds {
269 Some([bl, bt, br, bb]) => [bl.max(l), bt.max(t), br.min(r), bb.min(b)],
270 None => clip,
271 })
272 };
273 for t in &mut self.texts {
274 t.6 = narrow(t.6);
275 }
276 for i in &mut self.icons {
277 i.6 = narrow(i.6);
278 }
279 for st in &mut self.strokes {
280 st.2 = narrow(st.2);
281 }
282 self
283 }
284
285 /// A GPU-textured quad (id from `cce_ui::vk::upload_rgba`).
286 pub fn image(&mut self, id: u32, x: f32, y: f32, w: f32, h: f32, alpha: f32) {
287 self.images.push((id, x, y, w, h, alpha));
288 }
289
290 /// A cce-icons glyph (`folder`, `file-image`, …) at `(x, y, w, h)`, tinted
291 /// `color` as a label is — the one way this crate draws a symbol.
292 pub fn icon(&mut self, name: &str, x: f32, y: f32, w: f32, h: f32, color: [f32; 4]) {
293 self.icons.push((name.to_string(), x, y, w, h, color, None));
294 }
295
296 /// [`PageContent::icon`] cut to `bounds` `[l, t, r, b]`.
297 pub fn icon_bounded(&mut self, name: &str, x: f32, y: f32, w: f32, h: f32, color: [f32; 4], bounds: Option<[f32; 4]>) {
298 self.icons.push((name.to_string(), x, y, w, h, color, bounds));
299 }
300
301 pub fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
302 self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
303 }
304
305 /// [`PageContent::rect`] with a corner radius, applied only to `corners`
306 /// (top-left, top-right, bottom-right, bottom-left) — for a fill that has to
307 /// follow the rounded corner of the plate it sits inside.
308 pub fn rect_rounded(
309 &mut self,
310 color: [f32; 4],
311 x: f32,
312 y: f32,
313 w: f32,
314 h: f32,
315 radius: f32,
316 corners: (bool, bool, bool, bool),
317 ) {
318 self.rects.push((color, x, y, w, h, radius, corners));
319 }
320
321 /// A lit plate at (x, y, w, h): a rounded face in `color` plus the rolled,
322 /// lit perimeter — the treatment the pane plates wear. Unlike the relief
323 /// helpers this is NOT gated on `control_relief`: a plate owns the fill, so
324 /// skipping it would leave the surface unpainted rather than merely flat.
325 pub fn plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32) {
326 let depth = cce_ui::layout::bevel_width().min(h * 0.2);
327 self.plates.push((color, x, y, w, h, radius, depth));
328 }
329
330 /// Wall width for a carve whose corners are rounded at `radius`, capped so
331 /// the wall stays crease-free through the corner. A carve's wall straddles
332 /// the boundary, reaching `depth / 2` inward — and the inward offsets of a
333 /// squircle corner kink into a square crease past the corner's diagonal
334 /// curvature radius (`radius / corner_span_factor()`). The plate path
335 /// avoids this by widening its corner span instead, which a carve cannot:
336 /// its silhouette must stay on the widget's own nominal-radius corner.
337 /// Only tall rects ever feel the cap (a control's `h * 0.2` already lands
338 /// under it); the list well is the case that motivated it. Zero radius is
339 /// exempt — square corners meet in a miter by design.
340 fn carve_depth(h: f32, radius: f32) -> f32 {
341 let depth = cce_ui::layout::bevel_width().min(h * 0.2);
342 if radius > 0.0 {
343 depth.min(2.0 * radius / cce_ui::layout::corner_span_factor())
344 } else {
345 depth
346 }
347 }
348
349 /// A recessed well carved over the control at (x, y, w, h) — no-op when the
350 /// DE's control_relief styling is off.
351 pub fn relief_recessed(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
352 if cce_ui::layout::control_relief() {
353 let depth = Self::carve_depth(h, radius);
354 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RECESSED));
355 }
356 }
357
358 /// [`Self::relief_recessed`] for the well holding pointer focus: the ring
359 /// replaces the lighting (see [`RELIEF_RECESSED_FOCUS`]).
360 pub fn relief_recessed_focused(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
361 if cce_ui::layout::control_relief() {
362 let depth = Self::carve_depth(h, radius);
363 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RECESSED_FOCUS));
364 }
365 }
366
367 /// A raised plateau over the control at (x, y, w, h) — no-op when the DE's
368 /// control_relief styling is off.
369 pub fn relief_raised(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
370 if cce_ui::layout::control_relief() {
371 let depth = Self::carve_depth(h, radius);
372 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_RAISED));
373 }
374 }
375
376 /// A line engraved from `a` to `b` into the surface `host` — no-op when the
377 /// DE's control_relief styling is off.
378 pub fn groove(&mut self, a: (f32, f32), b: (f32, f32), width: f32, host: (f32, f32, f32, f32)) {
379 if cce_ui::layout::control_relief() {
380 let depth = cce_ui::layout::bevel_width().min(host.3 * 0.2);
381 self.grooves.push((a.0, a.1, b.0, b.1, width, depth, host.0, host.1, host.2, host.3));
382 }
383 }
384
385 /// A flush inset button plate (groove ring + beveled lip, face level with the
386 /// surface — the Button treatment) over the control at (x, y, w, h) — no-op
387 /// when the DE's control_relief styling is off.
388 pub fn relief_inset(&mut self, x: f32, y: f32, w: f32, h: f32, radius: f32) {
389 if cce_ui::layout::control_relief() {
390 let depth = Self::carve_depth(h, radius);
391 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_INSET));
392 }
393 }
394
395 pub fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
396 self.texts.push((content.to_string(), size, x, y, color, None, None));
397 }
398
399 pub fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
400 self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), None));
401 }
402
403 /// `text_with_font` with an explicit clip box `[l, t, r, b]` — for text
404 /// that scrolls under an edge and must render cut, not culled.
405 pub fn text_with_font_bounded(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, bounds: [f32; 4]) {
406 self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), Some(bounds)));
407 }
408
409 pub fn button(
410 &mut self,
411 label: &str,
412 x: f32,
413 y: f32,
414 w: f32,
415 h: f32,
416 bg: [f32; 4],
417 hover_bg: [f32; 4],
418 label_color: [f32; 4],
419 action: crate::Message,
420 ) {
421 let btn = cce_ui::widget::Button::new(x, y, w, h)
422 .with_label(label)
423 .with_bg(bg)
424 .with_hover_bg(hover_bg)
425 .with_label_color(label_color);
426 self.buttons.push((btn, action));
427 }
428
429 /// A button wearing the toolkit's own face — no per-call colors. The
430 /// hand-tinted variant above predates the themed Button; chrome buttons
431 /// (the chooser's Cancel/Save) should look like every other DE button.
432 pub fn button_plain(&mut self, label: &str, x: f32, y: f32, w: f32, h: f32, action: crate::Message) {
433 // Colorless chrome: transparent face over the theme's border/relief —
434 // the closed-dropdown convention — with a faint neutral hover. The
435 // toolkit's Primary face is itself blue-tinted, which is exactly what
436 // these buttons are not supposed to be.
437 let btn = cce_ui::widget::Button::new(x, y, w, h)
438 .with_label(label)
439 .with_bg([0.0, 0.0, 0.0, 0.0])
440 .with_hover_bg([1.0, 1.0, 1.0, 0.10]);
441 self.buttons.push((btn, action));
442 }
443
444 pub fn button_left(
445 &mut self,
446 label: &str,
447 x: f32,
448 y: f32,
449 w: f32,
450 h: f32,
451 bg: [f32; 4],
452 hover_bg: [f32; 4],
453 label_color: [f32; 4],
454 action: crate::Message,
455 ) {
456 let btn = cce_ui::widget::Button::new(x, y, w, h)
457 .with_label(label)
458 .with_bg(bg)
459 .with_hover_bg(hover_bg)
460 .with_label_color(label_color)
461 .with_left_align(true);
462 self.buttons.push((btn, action));
463 }
464 }
465
466 impl RenderTarget for PageContent {
467 fn icon(&mut self, name: &str, rect: cce_ui::scene::layout::Rect, color: [f32; 4]) {
468 PageContent::icon(self, name, rect.x, rect.y, rect.width, rect.height, color);
469 }
470
471 fn line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, thickness: f32, color: [f32; 4], cap: cce_ui::scene::paint::Cap) {
472 self.strokes.push((Stroke::Line { x1, y1, x2, y2, thickness, cap }, color, None));
473 }
474
475 fn arc(&mut self, cx: f32, cy: f32, radius: f32, thickness: f32, start: f32, end: f32, color: [f32; 4]) {
476 self.strokes.push((Stroke::Arc { cx, cy, radius, thickness, start, end }, color, None));
477 }
478
479 fn circle(&mut self, cx: f32, cy: f32, radius: f32, color: [f32; 4]) {
480 self.strokes.push((Stroke::Disc { cx, cy, radius }, color, None));
481 }
482
483 fn rect(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32) {
484 self.rects.push((color, x, y, w, h, 0.0, (true, true, true, true)));
485 }
486
487 fn rect_with_radius(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32) {
488 self.rects.push((color, x, y, w, h, radius, (true, true, true, true)));
489 }
490
491 fn rect_with_radius_corners(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, corners: (bool, bool, bool, bool)) {
492 self.rects.push((color, x, y, w, h, radius, corners));
493 }
494
495 fn text(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4]) {
496 self.texts.push((content.to_string(), size, x, y, color, None, None));
497 }
498
499 fn text_with_font(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str) {
500 self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), None));
501 }
502
503 fn text_with_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], bounds: Option<[f32; 4]>) {
504 self.texts.push((content.to_string(), size, x, y, color, None, bounds));
505 }
506
507 fn text_with_font_and_bounds(&mut self, content: &str, x: f32, y: f32, size: f32, color: [f32; 4], font: &str, bounds: Option<[f32; 4]>) {
508 self.texts.push((content.to_string(), size, x, y, color, Some(font.to_string()), bounds));
509 }
510
511 /// The flat-path bridge for a widget's flush inset plate — the Dropdown's
512 /// OPEN popover, which is its trigger surface grown over the unified box.
513 /// Without this override the default degrades it to a plain rounded fill,
514 /// so the menu lost the groove ring and lip the closed trigger has the
515 /// moment it expanded.
516 ///
517 /// The face and the walls go to different vecs on purpose — `reliefs` are
518 /// edges-only, drawn over the faces `rects` own — and `display_list` emits
519 /// this part's rects before its reliefs, so one call here lands as fill
520 /// then ring, in that order, within whichever part is being collected.
521 ///
522 /// **The caller's `depth` is used verbatim, NOT re-derived from `h`.** The
523 /// widget computes it from the TRIGGER's height; the box handed here is the
524 /// trigger plus the revealed menu, several times taller. `relief_inset`
525 /// would recompute `bevel_width().min(h * 0.2)` off that expanded height
526 /// and thicken the ring as the menu grows. A ring that swells during the open
527 /// animation is exactly the artifact this override exists to avoid.
528 fn inset_plate(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32) {
529 self.rects.push((color, x, y, w, h, radius, (true, true, true, true)));
530 if cce_ui::layout::control_relief() {
531 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_INSET));
532 }
533 }
534 /// The focused control plate's ring — same plate, rim lit.
535 fn inset_plate_tinted(&mut self, color: [f32; 4], x: f32, y: f32, w: f32, h: f32, radius: f32, depth: f32, _tint: [f32; 3]) {
536 self.rects.push((color, x, y, w, h, radius, (true, true, true, true)));
537 if cce_ui::layout::control_relief() {
538 self.reliefs.push((x, y, w, h, radius, depth, RELIEF_INSET_FOCUS));
539 }
540 }
541 }