use std::collections::HashSet;
pub const INPUT_WIDTH: f32 = 300.0;
pub const SLIDER_WIDTH: f32 = 250.0;
pub const SLIDER_HEIGHT: f32 = 18.0;
pub const COMBO_WIDTH: f32 = 200.0;
pub struct CollapsibleSection<'a> {
title: &'a str,
id: &'a str,
default_open: bool,
collapsed_sections: &'a mut HashSet<String>,
search_query: &'a str,
keywords: &'a [&'a str],
}
impl<'a> CollapsibleSection<'a> {
pub fn new(
title: &'a str,
id: &'a str,
collapsed_sections: &'a mut HashSet<String>,
search_query: &'a str,
) -> Self {
Self {
title,
id,
default_open: true,
collapsed_sections,
search_query,
keywords: &[],
}
}
pub fn default_open(mut self, open: bool) -> Self {
self.default_open = open;
self
}
pub fn keywords(mut self, keywords: &'a [&'a str]) -> Self {
self.keywords = keywords;
self
}
pub fn matches_search(&self) -> bool {
if self.search_query.is_empty() {
return true;
}
let query = self.search_query.to_lowercase();
if self.title.to_lowercase().contains(&query) {
return true;
}
self.keywords
.iter()
.any(|k| k.to_lowercase().contains(&query))
}
pub fn show<R>(
self,
ui: &mut egui::Ui,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> Option<egui::CollapsingResponse<R>> {
if !self.matches_search() {
return None;
}
let is_collapsed = self.collapsed_sections.contains(self.id);
let should_be_open = if self.search_query.is_empty() {
!is_collapsed && self.default_open || is_collapsed && !self.default_open
} else {
true
};
let header = egui::CollapsingHeader::new(self.title)
.id_salt(self.id)
.default_open(should_be_open);
let response = header.show(ui, add_contents);
let section_id = self.id.to_string();
if response.header_response.clicked() {
if self.collapsed_sections.contains(§ion_id) {
self.collapsed_sections.remove(§ion_id);
} else {
self.collapsed_sections.insert(section_id);
}
}
Some(response)
}
}
pub fn section_matches(query: &str, title: &str, keywords: &[&str]) -> bool {
if query.is_empty() {
return true;
}
if title.to_lowercase().contains(query) {
return true;
}
keywords.iter().any(|k| k.to_lowercase().contains(query))
}
pub fn collapsing_section<R>(
ui: &mut egui::Ui,
title: &str,
id: &str,
default_open: bool,
collapsed_sections: &mut HashSet<String>,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::CollapsingResponse<R> {
let is_toggled = collapsed_sections.contains(id);
let should_be_open = is_toggled != default_open;
let response = egui::CollapsingHeader::new(title)
.id_salt(id)
.default_open(should_be_open)
.show(ui, add_contents);
if response.header_response.clicked() {
let section_id = id.to_string();
if collapsed_sections.contains(§ion_id) {
collapsed_sections.remove(§ion_id);
} else {
collapsed_sections.insert(section_id);
}
}
response
}
pub fn collapsing_section_with_state<R>(
ui: &mut egui::Ui,
title: &str,
id: &str,
default_open: bool,
collapsed_sections: &mut HashSet<String>,
add_contents: impl FnOnce(&mut egui::Ui, &mut HashSet<String>) -> R,
) -> egui::CollapsingResponse<R> {
let is_toggled = collapsed_sections.contains(id);
let should_be_open = is_toggled != default_open;
let id_owned = id.to_string();
let response = egui::CollapsingHeader::new(title)
.id_salt(id)
.default_open(should_be_open)
.show(ui, |ui| add_contents(ui, collapsed_sections));
if response.header_response.clicked() {
if collapsed_sections.contains(&id_owned) {
collapsed_sections.remove(&id_owned);
} else {
collapsed_sections.insert(id_owned);
}
}
response
}
pub fn section_heading(ui: &mut egui::Ui, title: &str) {
ui.add_space(8.0);
ui.heading(title);
ui.add_space(4.0);
}
pub fn subsection_label(ui: &mut egui::Ui, title: &str) {
ui.add_space(8.0);
ui.label(egui::RichText::new(title).strong());
ui.add_space(4.0);
}
pub fn section_spacing(ui: &mut egui::Ui) {
ui.add_space(12.0);
}
pub fn indented<R>(
ui: &mut egui::Ui,
id: impl std::hash::Hash,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> egui::InnerResponse<R> {
ui.indent(id, add_contents)
}