parent
539d83de47
commit
282a5176af
4 changed files with 77 additions and 47 deletions
|
|
@ -175,23 +175,36 @@ impl Scene {
|
||||||
count
|
count
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `handle` belongs to any selectable groups, also select all other members of those groups.
|
/// Return `handles` plus every member of a selectable group containing one
|
||||||
pub fn expand_selection_for_groups(&mut self, handles: &[Handle]) {
|
/// of them. Selection and rollover highlighting share this expansion so the
|
||||||
let to_add: Vec<Handle> = self
|
/// preview matches what a click will select.
|
||||||
.document
|
pub fn handles_expanded_for_selectable_groups(
|
||||||
|
&self,
|
||||||
|
handles: &[Handle],
|
||||||
|
) -> HashSet<Handle> {
|
||||||
|
let mut expanded: HashSet<Handle> = handles.iter().copied().collect();
|
||||||
|
expanded.extend(
|
||||||
|
self.document
|
||||||
.objects
|
.objects
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|obj| match obj {
|
.filter_map(|obj| match obj {
|
||||||
ObjectType::Group(g) if g.selectable && handles.iter().any(|h| g.contains(*h)) => {
|
ObjectType::Group(g)
|
||||||
|
if g.selectable
|
||||||
|
&& handles.iter().any(|handle| g.contains(*handle)) =>
|
||||||
|
{
|
||||||
Some(g.entities.clone())
|
Some(g.entities.clone())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten(),
|
||||||
.collect();
|
);
|
||||||
for h in to_add {
|
expanded
|
||||||
self.selected.insert(h);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If any handle belongs to a selectable group, also select every member.
|
||||||
|
pub fn expand_selection_for_groups(&mut self, handles: &[Handle]) {
|
||||||
|
self.selected
|
||||||
|
.extend(self.handles_expanded_for_selectable_groups(handles));
|
||||||
self.bump_selection();
|
self.bump_selection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4235,18 +4235,33 @@ impl Scene {
|
||||||
if self.hover_highlight == handle {
|
if self.hover_highlight == handle {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Hover is folded into the highlight set (selected ∪ {hover}) that
|
// Selectable groups roll over as one unit, matching click selection.
|
||||||
// drives the xray overlay. A hover handle that's already selected
|
// Exclude already-selected members because they already contribute to
|
||||||
// contributes nothing, so the effective set is unchanged — skip the
|
// the overlay and remain in the selected colour.
|
||||||
// overlay refresh then. The field is still updated for hit-test / UI.
|
let contribution = |scene: &Scene, hovered: Option<Handle>| {
|
||||||
let contribution = |h: Option<Handle>| h.filter(|h| !self.selected.contains(h));
|
hovered
|
||||||
let changed = contribution(self.hover_highlight) != contribution(handle);
|
.map(|handle| scene.handles_expanded_for_selectable_groups(&[handle]))
|
||||||
|
.unwrap_or_default()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !scene.selected.contains(handle))
|
||||||
|
.collect::<HashSet<_>>()
|
||||||
|
};
|
||||||
|
let changed = contribution(self, self.hover_highlight) != contribution(self, handle);
|
||||||
self.hover_highlight = handle;
|
self.hover_highlight = handle;
|
||||||
if changed {
|
if changed {
|
||||||
self.bump_selection();
|
self.bump_selection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles that currently contribute the rollover colour. The stored hover
|
||||||
|
/// remains the picked member for hit-test/UI bookkeeping; rendering expands
|
||||||
|
/// it to the same selectable group that a click would select.
|
||||||
|
pub fn hover_highlight_handles(&self) -> HashSet<Handle> {
|
||||||
|
self.hover_highlight
|
||||||
|
.map(|handle| self.handles_expanded_for_selectable_groups(&[handle]))
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Keep the current selection visible and temporarily filter every other
|
/// Keep the current selection visible and temporarily filter every other
|
||||||
/// top-level object. Block-definition children are intentionally not added
|
/// top-level object. Block-definition children are intentionally not added
|
||||||
/// here: an isolated INSERT remains a complete visible block instance.
|
/// here: an isolated INSERT remains a complete visible block instance.
|
||||||
|
|
|
||||||
|
|
@ -1793,13 +1793,12 @@ impl Pipeline {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
hover: Option<acadrust::Handle>,
|
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
annotation_context_wires: &[WireModel],
|
annotation_context_wires: &[WireModel],
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) {
|
) {
|
||||||
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
||||||
let hover = hover.filter(|h| !selected.contains(h));
|
if selected.is_empty() && hovered.is_empty() && annotation_context_wires.is_empty() {
|
||||||
if selected.is_empty() && hover.is_none() && annotation_context_wires.is_empty() {
|
|
||||||
self.gpu_selected_wires = vec![];
|
self.gpu_selected_wires = vec![];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1818,7 +1817,7 @@ impl Pipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(h) = hover {
|
for h in hovered.iter().filter(|handle| !selected.contains(handle)) {
|
||||||
if let Some(idxs) = self.wire_handle_index.get(&h.value()) {
|
if let Some(idxs) = self.wire_handle_index.get(&h.value()) {
|
||||||
let mut slots = idxs.clone();
|
let mut slots = idxs.clone();
|
||||||
slots.sort_unstable();
|
slots.sort_unstable();
|
||||||
|
|
@ -1857,7 +1856,11 @@ impl Pipeline {
|
||||||
crate::perf_record!(
|
crate::perf_record!(
|
||||||
"[perf] wire-highlight {:>7.1}ms handles={} wires={}",
|
"[perf] wire-highlight {:>7.1}ms handles={} wires={}",
|
||||||
elapsed_ms,
|
elapsed_ms,
|
||||||
selected.len() + usize::from(hover.is_some()),
|
selected.len()
|
||||||
|
+ hovered
|
||||||
|
.iter()
|
||||||
|
.filter(|handle| !selected.contains(handle))
|
||||||
|
.count(),
|
||||||
selected_wires.len() + hover_wires.len(),
|
selected_wires.len() + hover_wires.len(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1873,12 +1876,11 @@ impl Pipeline {
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
hover: Option<acadrust::Handle>,
|
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
annotation_context_wires: &[WireModel],
|
annotation_context_wires: &[WireModel],
|
||||||
) {
|
) {
|
||||||
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
||||||
let hover = hover.filter(|h| !selected.contains(h));
|
if selected.is_empty() && hovered.is_empty() && annotation_context_wires.is_empty() {
|
||||||
if selected.is_empty() && hover.is_none() && annotation_context_wires.is_empty() {
|
|
||||||
self.text_highlight_vbuf = None;
|
self.text_highlight_vbuf = None;
|
||||||
self.text_highlight_vcount = 0;
|
self.text_highlight_vcount = 0;
|
||||||
return;
|
return;
|
||||||
|
|
@ -1902,7 +1904,7 @@ impl Pipeline {
|
||||||
for h in selected {
|
for h in selected {
|
||||||
push(h.value(), WireModel::SELECTED, wires, &mut out);
|
push(h.value(), WireModel::SELECTED, wires, &mut out);
|
||||||
}
|
}
|
||||||
if let Some(h) = hover {
|
for h in hovered.iter().filter(|handle| !selected.contains(handle)) {
|
||||||
push(h.value(), WireModel::HOVER, wires, &mut out);
|
push(h.value(), WireModel::HOVER, wires, &mut out);
|
||||||
}
|
}
|
||||||
for wire in annotation_context_wires {
|
for wire in annotation_context_wires {
|
||||||
|
|
@ -2638,7 +2640,7 @@ impl Pipeline {
|
||||||
pub fn update_mesh_highlight(
|
pub fn update_mesh_highlight(
|
||||||
&mut self,
|
&mut self,
|
||||||
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
selected: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
hover: Option<acadrust::Handle>,
|
hovered: &rustc_hash::FxHashSet<acadrust::Handle>,
|
||||||
) {
|
) {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
for handle in selected {
|
for handle in selected {
|
||||||
|
|
@ -2649,7 +2651,7 @@ impl Pipeline {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(handle) = hover.filter(|handle| !selected.contains(handle)) {
|
for handle in hovered.iter().filter(|handle| !selected.contains(handle)) {
|
||||||
if let Some(ranges) = self.mesh_ranges_by_handle.get(&handle) {
|
if let Some(ranges) = self.mesh_ranges_by_handle.get(&handle) {
|
||||||
out.extend(ranges.iter().copied().map(|range| MeshHighlightDraw {
|
out.extend(ranges.iter().copied().map(|range| MeshHighlightDraw {
|
||||||
range,
|
range,
|
||||||
|
|
|
||||||
|
|
@ -137,8 +137,8 @@ pub struct ViewportData {
|
||||||
pub(in crate::scene) wire_patch: Option<(u64, Arc<crate::scene::WireGpuPatch>)>,
|
pub(in crate::scene) wire_patch: Option<(u64, Arc<crate::scene::WireGpuPatch>)>,
|
||||||
/// Selected handles only (no hover) — solid meshes tint these blue.
|
/// Selected handles only (no hover) — solid meshes tint these blue.
|
||||||
pub(in crate::scene) selected_handles: Arc<rustc_hash::FxHashSet<acadrust::Handle>>,
|
pub(in crate::scene) selected_handles: Arc<rustc_hash::FxHashSet<acadrust::Handle>>,
|
||||||
/// Currently hovered handle — solid meshes tint it orange.
|
/// Currently hovered selectable unit — solid meshes tint it orange.
|
||||||
pub(in crate::scene) hover_handle: Option<acadrust::Handle>,
|
pub(in crate::scene) hover_handles: Arc<rustc_hash::FxHashSet<acadrust::Handle>>,
|
||||||
/// Bumped on selection / hover change. Paired with `wire_content_id` to
|
/// Bumped on selection / hover change. Paired with `wire_content_id` to
|
||||||
/// decide when the xray overlay batch needs rebuilding.
|
/// decide when the xray overlay batch needs rebuilding.
|
||||||
pub(in crate::scene) selection_generation: u64,
|
pub(in crate::scene) selection_generation: u64,
|
||||||
|
|
@ -789,12 +789,12 @@ impl shader::Primitive for Primitive {
|
||||||
// wire buffers.
|
// wire buffers.
|
||||||
let sel_key = (vp.wire_content_id, vp.selection_generation);
|
let sel_key = (vp.wire_content_id, vp.selection_generation);
|
||||||
let highlighted_geometry_unchanged =
|
let highlighted_geometry_unchanged =
|
||||||
vp.selected_handles.is_empty() && vp.hover_handle.is_none()
|
vp.selected_handles.is_empty() && vp.hover_handles.is_empty()
|
||||||
|| vp.wire_patch.as_ref().is_some_and(|(previous, patch)| {
|
|| vp.wire_patch.as_ref().is_some_and(|(previous, patch)| {
|
||||||
*previous == inner.cached_selection.0
|
*previous == inner.cached_selection.0
|
||||||
&& patch.changes.iter().all(|(handle, _)| {
|
&& patch.changes.iter().all(|(handle, _)| {
|
||||||
!vp.selected_handles.contains(handle)
|
!vp.selected_handles.contains(handle)
|
||||||
&& vp.hover_handle != Some(*handle)
|
&& !vp.hover_handles.contains(handle)
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
let selection_changed = inner.cached_selection.1 != vp.selection_generation;
|
let selection_changed = inner.cached_selection.1 != vp.selection_generation;
|
||||||
|
|
@ -813,7 +813,7 @@ impl shader::Primitive for Primitive {
|
||||||
device,
|
device,
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
&vp.selected_handles,
|
&vp.selected_handles,
|
||||||
vp.hover_handle,
|
&vp.hover_handles,
|
||||||
&vp.annotation_context_wires,
|
&vp.annotation_context_wires,
|
||||||
&draw_depths,
|
&draw_depths,
|
||||||
);
|
);
|
||||||
|
|
@ -824,7 +824,7 @@ impl shader::Primitive for Primitive {
|
||||||
device,
|
device,
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
&vp.selected_handles,
|
&vp.selected_handles,
|
||||||
vp.hover_handle,
|
&vp.hover_handles,
|
||||||
&vp.annotation_context_wires,
|
&vp.annotation_context_wires,
|
||||||
);
|
);
|
||||||
inner.cached_annotation_highlight_source =
|
inner.cached_annotation_highlight_source =
|
||||||
|
|
@ -864,7 +864,7 @@ impl shader::Primitive for Primitive {
|
||||||
vp.selection_generation,
|
vp.selection_generation,
|
||||||
);
|
);
|
||||||
if hl_key != inner.cached_highlight_key {
|
if hl_key != inner.cached_highlight_key {
|
||||||
inner.update_mesh_highlight(&vp.selected_handles, vp.hover_handle);
|
inner.update_mesh_highlight(&vp.selected_handles, &vp.hover_handles);
|
||||||
inner.cached_highlight_key = hl_key;
|
inner.cached_highlight_key = hl_key;
|
||||||
}
|
}
|
||||||
// Live overlay (command preview / interim / grip drag) — small and
|
// Live overlay (command preview / interim / grip drag) — small and
|
||||||
|
|
@ -1978,12 +1978,12 @@ impl Scene {
|
||||||
.copied()
|
.copied()
|
||||||
.map(|handle| (handle, true))
|
.map(|handle| (handle, true))
|
||||||
.collect();
|
.collect();
|
||||||
if let Some(handle) = self
|
highlighted.extend(
|
||||||
.hover_highlight
|
self.hover_highlight_handles()
|
||||||
|
.into_iter()
|
||||||
.filter(|handle| !self.selected.contains(handle))
|
.filter(|handle| !self.selected.contains(handle))
|
||||||
{
|
.map(|handle| (handle, false)),
|
||||||
highlighted.push((handle, false));
|
);
|
||||||
}
|
|
||||||
highlighted.sort_unstable_by_key(|(handle, _)| handle.value());
|
highlighted.sort_unstable_by_key(|(handle, _)| handle.value());
|
||||||
|
|
||||||
let empty_selection = rustc_hash::FxHashSet::default();
|
let empty_selection = rustc_hash::FxHashSet::default();
|
||||||
|
|
@ -2581,7 +2581,7 @@ impl Scene {
|
||||||
wire_content_id,
|
wire_content_id,
|
||||||
wire_patch,
|
wire_patch,
|
||||||
selected_handles: Arc::new(self.selected.iter().copied().collect()),
|
selected_handles: Arc::new(self.selected.iter().copied().collect()),
|
||||||
hover_handle: self.hover_highlight,
|
hover_handles: Arc::new(self.hover_highlight_handles()),
|
||||||
selection_generation: self.selection_generation,
|
selection_generation: self.selection_generation,
|
||||||
selected_sig: self.selected_set_sig(),
|
selected_sig: self.selected_set_sig(),
|
||||||
screen_rect,
|
screen_rect,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue