From f9c1c3cb2485d8f2fce464b57e04291507898d4a Mon Sep 17 00:00:00 2001 From: jyviko <2501648+jyviko@users.noreply.github.com> Date: Thu, 15 Oct 2026 09:46:33 -0400 Subject: [PATCH] ui: the selection filter follows what the selection holds (#341, #371) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo --- crates/ui-egui/src/lib.rs | 5 +++++ crates/ui-egui/src/selection.rs | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index a1ef29c..6052ecb 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -1264,6 +1264,11 @@ impl ModelApp { if self.pick.selection.apply(items, mode) { self.pick.generation = self.pick.generation.wrapping_add(1); } + // The filter label follows what the selection holds (a seed picked in the browser selects + // its edge whatever the filter was, #341). + if let Some(f) = self.pick.selection.filter() { + self.pick.filter = f; + } let mut text = format!("Selected: {}", self.pick.selection.summary()); if self.pick.selection.capped() { text.push_str(&format!(" (at most {} items)", selection::MAX_SELECTION)); diff --git a/crates/ui-egui/src/selection.rs b/crates/ui-egui/src/selection.rs index 7d910ff..7725b6d 100644 --- a/crates/ui-egui/src/selection.rs +++ b/crates/ui-egui/src/selection.rs @@ -72,6 +72,21 @@ impl Filter { } } + /// The filter that picks `item`. + pub fn of(item: &Item) -> Filter { + match item { + Item::Geometry(g) => match g.entity { + Entity::Body => Filter::Bodies, + Entity::Face(_) => Filter::Faces, + Entity::Edge(_) => Filter::Edges, + Entity::Vertex(_) => Filter::Vertices, + }, + Item::Node(_) => Filter::Nodes, + Item::Element(_) => Filter::Elements, + Item::Face(_) => Filter::ElementFaces, + } + } + /// Geometry (named) rather than mesh (labelled) entities. pub fn geometry(self) -> bool { matches!(self, Filter::Faces | Filter::Edges | Filter::Vertices | Filter::Bodies) @@ -142,6 +157,13 @@ impl Selection { &self.items } + /// The one filter that picks every selected item (`None` when empty or mixed). + pub fn filter(&self) -> Option { + let mut kinds = self.items.iter().map(Filter::of); + let first = kinds.next()?; + kinds.all(|k| k == first).then_some(first) + } + pub fn len(&self) -> usize { self.items.len() } @@ -974,6 +996,20 @@ mod tests { use super::*; + /// The filter a selection holds: one kind names it, a mix or nothing names none (#341). + #[test] + fn a_selection_names_the_filter_of_what_it_holds() { + let edge = Item::Geometry(GeometryRef::new("Bar", Entity::Edge("top".into())).unwrap()); + let mut sel = Selection::default(); + assert_eq!(sel.filter(), None); + sel.apply([edge], Mode::Replace); + assert_eq!(sel.filter(), Some(Filter::Edges)); + sel.apply([Item::Node(3)], Mode::Add); + assert_eq!(sel.filter(), None); + sel.apply([Item::Node(4)], Mode::Replace); + assert_eq!(sel.filter(), Some(Filter::Nodes)); + } + /// `nx × ny × nz` unit hex8 cells, the faces at x = nx tagged as face `end` of body `Bar`, the /// solids as the body, the edge along x = nx, z = nz as edge `top` and the corner node at /// (nx, 0, 0) as vertex `v`.