refactor(text): per-entity SDF text — all text kinds, selection/hover highlight
Rearchitect SDF text from a document-wide collector to per-entity production during tessellation, so each entity owns its glyph quads and composite/nested text is covered for free (no re-explosion, no per-frame document walk). - WireModel gains `text_verts`: glyph quads ride on the wire, so they are cached by the tess memo, cloned on hit, and transformed by the block expand loop exactly like `points`. - Text arm (tessellate.rs) builds the quads when SDF is on and suppresses the strokes; render gathers text_verts from the viewport wire set (`gather_text_verts`, cached on wire content id) — the old collector is removed. Covers model, dimension, table, block-internal and paper-sheet text uniformly. - MLEADER text (lays out via layout_mtext, not the Text arm) gets its own SDF branch driven by the per-run GlyphRun. - Pick box comes from the rendered glyph quads (true text extent), not entity_aabb which mis-places the box for MTEXT; composite paths no longer clobber the empty text wire's tight AABB (dim pick box hugs the text). - Selection / hover highlight via a text-highlight overlay (upload_text_ highlight, keyed on selection_generation) mirroring the selected-wire xray overlay; base glyphs stay neutral so a deselect leaves no stale tint. - Debug: env OCS_TEXT_BOX draws each run's glyph-bounds rectangle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
fd0b8fc1d4
commit
110683f419
47 changed files with 528 additions and 142 deletions
|
|
@ -85,6 +85,11 @@ pub struct Pipeline {
|
|||
/// All glyph-quad vertices for the frame, one buffer, `None` when empty.
|
||||
text_vbuf: Option<wgpu::Buffer>,
|
||||
text_vcount: u32,
|
||||
/// Tinted glyph quads of just the selected / hovered text, drawn over the
|
||||
/// base text pass so a selection / rollover recolours the glyphs (the text
|
||||
/// analogue of the selected-wire xray overlay). Rebuilt on selection change.
|
||||
text_highlight_vbuf: Option<wgpu::Buffer>,
|
||||
text_highlight_vcount: u32,
|
||||
depth_texture_size: Size<u32>,
|
||||
depth_view: wgpu::TextureView,
|
||||
/// 4× MSAA color buffer for the main drawing passes.
|
||||
|
|
@ -1146,6 +1151,8 @@ impl Pipeline {
|
|||
text_atlas_gpu: None,
|
||||
text_vbuf: None,
|
||||
text_vcount: 0,
|
||||
text_highlight_vbuf: None,
|
||||
text_highlight_vcount: 0,
|
||||
mesh_pipeline,
|
||||
mesh_transparent_pipeline,
|
||||
mesh_highlight_pipeline,
|
||||
|
|
@ -1306,6 +1313,49 @@ impl Pipeline {
|
|||
WireGpu::from_run(device, &out, depth_map, None, false, self.wire_const_bgl.as_ref());
|
||||
}
|
||||
|
||||
/// Build the text-highlight overlay: the glyph quads of just the selected /
|
||||
/// hovered text, recoloured (selection blue, hover orange) and drawn over
|
||||
/// the base text pass. Uses the same handle→wire index as the selected-wire
|
||||
/// overlay, so it is O(highlighted). Empty when nothing is highlighted.
|
||||
pub fn upload_text_highlight(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
wires: &[WireModel],
|
||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||
hover: Option<acadrust::Handle>,
|
||||
) {
|
||||
let hover = hover.filter(|h| !selected.contains(h));
|
||||
if selected.is_empty() && hover.is_none() {
|
||||
self.text_highlight_vbuf = None;
|
||||
self.text_highlight_vcount = 0;
|
||||
return;
|
||||
}
|
||||
let mut out: Vec<text_gpu::TextVertex> = Vec::new();
|
||||
let push =
|
||||
|handle_val: u64, tint: [f32; 4], wires: &[WireModel], out: &mut Vec<text_gpu::TextVertex>| {
|
||||
if let Some(idxs) = self.wire_handle_index.get(&handle_val) {
|
||||
for &i in idxs {
|
||||
if let Some(w) = wires.get(i as usize) {
|
||||
for v in &w.text_verts {
|
||||
out.push(text_gpu::TextVertex {
|
||||
color: [tint[0], tint[1], tint[2], v.color[3]],
|
||||
..*v
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for h in selected {
|
||||
push(h.value(), WireModel::SELECTED, wires, &mut out);
|
||||
}
|
||||
if let Some(h) = hover {
|
||||
push(h.value(), WireModel::HOVER, wires, &mut out);
|
||||
}
|
||||
self.text_highlight_vcount = out.len() as u32;
|
||||
self.text_highlight_vbuf = text_gpu::upload_vertices(device, &out);
|
||||
}
|
||||
|
||||
/// Upload the live overlay (command preview / interim / grip-drag) wires.
|
||||
/// Small and refreshed each frame they're present; kept separate from the
|
||||
/// base wire buffer so a drag never re-uploads the resident base set.
|
||||
|
|
@ -2066,8 +2116,12 @@ impl Pipeline {
|
|||
}
|
||||
|
||||
// ── Pass 5c: SDF text quads (drawn over wires) ────────────────────
|
||||
if let (Some(vbuf), Some(atlas)) = (&self.text_vbuf, &self.text_atlas_gpu) {
|
||||
if self.text_vcount > 0 {
|
||||
// The highlight buffer (selected / hovered text, tinted) is drawn in
|
||||
// the same pass right after the base text so it recolours those glyphs.
|
||||
if let Some(atlas) = &self.text_atlas_gpu {
|
||||
let have_base = self.text_vbuf.is_some() && self.text_vcount > 0;
|
||||
let have_hl = self.text_highlight_vbuf.is_some() && self.text_highlight_vcount > 0;
|
||||
if have_base || have_hl {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("text.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
|
|
@ -2094,8 +2148,18 @@ impl Pipeline {
|
|||
pass.set_pipeline(&self.text_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_bind_group(1, &atlas.bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, vbuf.slice(..));
|
||||
pass.draw(0..self.text_vcount, 0..1);
|
||||
if let Some(vbuf) = &self.text_vbuf {
|
||||
if self.text_vcount > 0 {
|
||||
pass.set_vertex_buffer(0, vbuf.slice(..));
|
||||
pass.draw(0..self.text_vcount, 0..1);
|
||||
}
|
||||
}
|
||||
if let Some(hlbuf) = &self.text_highlight_vbuf {
|
||||
if self.text_highlight_vcount > 0 {
|
||||
pass.set_vertex_buffer(0, hlbuf.slice(..));
|
||||
pass.draw(0..self.text_highlight_vcount, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue