use crate::widget::high_level::PlotItemKind;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SelectableItem {
pub label: String,
pub kind: PlotItemKind,
pub selected: bool,
}
impl SelectableItem {
pub fn new(label: impl Into<String>, kind: PlotItemKind, selected: bool) -> Self {
Self {
label: label.into(),
kind,
selected,
}
}
}
const KIND_ORDER: [PlotItemKind; 8] = [
PlotItemKind::Curve,
PlotItemKind::Image,
PlotItemKind::Scatter,
PlotItemKind::Histogram,
PlotItemKind::Marker,
PlotItemKind::Mask,
PlotItemKind::Triangles,
PlotItemKind::Shape,
];
fn distinct_kinds(items: &[SelectableItem]) -> Vec<PlotItemKind> {
KIND_ORDER
.into_iter()
.filter(|k| items.iter().any(|it| it.kind == *k))
.collect()
}
pub struct ItemsSelectionDialog {
items: Vec<SelectableItem>,
shown_kinds: Vec<PlotItemKind>,
}
impl ItemsSelectionDialog {
pub fn new(items: Vec<SelectableItem>) -> Self {
let shown_kinds = distinct_kinds(&items);
Self { items, shown_kinds }
}
pub fn set_items(&mut self, items: Vec<SelectableItem>) {
self.shown_kinds = distinct_kinds(&items);
self.items = items;
}
pub fn items(&self) -> &[SelectableItem] {
&self.items
}
pub fn available_kinds(&self) -> Vec<PlotItemKind> {
distinct_kinds(&self.items)
}
pub fn is_kind_shown(&self, kind: PlotItemKind) -> bool {
self.shown_kinds.contains(&kind)
}
pub fn set_kind_shown(&mut self, kind: PlotItemKind, shown: bool) {
let present = self.shown_kinds.iter().position(|k| *k == kind);
match (shown, present) {
(true, None) => self.shown_kinds.push(kind),
(false, Some(i)) => {
self.shown_kinds.remove(i);
}
_ => {}
}
}
pub fn set_selected(&mut self, index: usize, selected: bool) {
if let Some(item) = self.items.get_mut(index) {
item.selected = selected;
}
}
pub fn toggle_selected(&mut self, index: usize) {
if let Some(item) = self.items.get_mut(index) {
item.selected = !item.selected;
}
}
pub fn selected_items(&self) -> impl Iterator<Item = &SelectableItem> {
self.items
.iter()
.filter(|it| it.selected && self.shown_kinds.contains(&it.kind))
}
pub fn visible_groups(&self) -> Vec<(PlotItemKind, Vec<usize>)> {
let mut groups = Vec::new();
for kind in KIND_ORDER {
if !self.shown_kinds.contains(&kind) {
continue;
}
let indices: Vec<usize> = self
.items
.iter()
.enumerate()
.filter(|(_, it)| it.kind == kind)
.map(|(i, _)| i)
.collect();
if !indices.is_empty() {
groups.push((kind, indices));
}
}
groups
}
pub fn ui(&mut self, ui: &mut egui::Ui) {
ui.label("Filter item kinds:");
ui.horizontal_wrapped(|ui| {
for kind in self.available_kinds() {
let mut shown = self.is_kind_shown(kind);
if ui.checkbox(&mut shown, kind.as_str()).changed() {
self.set_kind_shown(kind, shown);
}
}
});
ui.separator();
ui.label("Select items:");
let groups = self.visible_groups();
if groups.is_empty() {
ui.weak("No items");
return;
}
egui::ScrollArea::vertical().show(ui, |ui| {
for (kind, indices) in groups {
ui.strong(kind.as_str());
for index in indices {
let mut selected = self.items[index].selected;
let label = self.items[index].label.clone();
if ui.checkbox(&mut selected, label).changed() {
self.set_selected(index, selected);
}
}
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> ItemsSelectionDialog {
ItemsSelectionDialog::new(vec![
SelectableItem::new("curve A", PlotItemKind::Curve, true),
SelectableItem::new("curve B", PlotItemKind::Curve, false),
SelectableItem::new("image A", PlotItemKind::Image, true),
SelectableItem::new("scatter A", PlotItemKind::Scatter, false),
])
}
#[test]
fn all_kinds_shown_by_default() {
let d = sample();
assert!(d.is_kind_shown(PlotItemKind::Curve));
assert!(d.is_kind_shown(PlotItemKind::Image));
assert!(d.is_kind_shown(PlotItemKind::Scatter));
assert!(!d.is_kind_shown(PlotItemKind::Marker));
}
#[test]
fn available_kinds_are_distinct_in_kind_order() {
let d = sample();
assert_eq!(
d.available_kinds(),
vec![
PlotItemKind::Curve,
PlotItemKind::Image,
PlotItemKind::Scatter
]
);
}
#[test]
fn selected_items_returns_only_selected_and_shown() {
let d = sample();
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["curve A", "image A"]);
}
#[test]
fn toggling_selection_changes_the_returned_subset() {
let mut d = sample();
d.toggle_selected(1);
d.set_selected(2, false);
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["curve A", "curve B"]);
}
#[test]
fn set_and_toggle_out_of_range_are_noops() {
let mut d = sample();
d.set_selected(99, true); d.toggle_selected(99);
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["curve A", "image A"]);
}
#[test]
fn hiding_a_kind_drops_its_items_from_the_subset_without_clearing_selection() {
let mut d = sample();
d.set_kind_shown(PlotItemKind::Curve, false);
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["image A"]);
d.set_kind_shown(PlotItemKind::Curve, true);
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["curve A", "image A"]);
}
#[test]
fn visible_groups_filters_and_groups_in_kind_order() {
let mut d = sample();
let groups = d.visible_groups();
assert_eq!(
groups,
vec![
(PlotItemKind::Curve, vec![0, 1]),
(PlotItemKind::Image, vec![2]),
(PlotItemKind::Scatter, vec![3]),
]
);
d.set_kind_shown(PlotItemKind::Image, false);
let groups = d.visible_groups();
assert_eq!(
groups,
vec![
(PlotItemKind::Curve, vec![0, 1]),
(PlotItemKind::Scatter, vec![3]),
]
);
}
#[test]
fn set_items_reshows_every_new_kind() {
let mut d = sample();
d.set_kind_shown(PlotItemKind::Curve, false);
d.set_items(vec![
SelectableItem::new("m", PlotItemKind::Marker, true),
SelectableItem::new("c", PlotItemKind::Curve, true),
]);
assert!(d.is_kind_shown(PlotItemKind::Marker));
assert!(d.is_kind_shown(PlotItemKind::Curve));
let labels: Vec<&str> = d.selected_items().map(|it| it.label.as_str()).collect();
assert_eq!(labels, vec!["m", "c"]);
}
}