1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
use crate::{list_item, UiExt as _};
/// A collapsible section header, with support for optional help tooltip and button.
///
/// It toggles on click.
#[allow(clippy::type_complexity)]
pub struct SectionCollapsingHeader<'a> {
label: egui::WidgetText,
default_open: bool,
button: Option<Box<dyn list_item::ItemButton + 'a>>,
help: Option<Box<dyn FnOnce(&mut egui::Ui) + 'a>>,
}
impl<'a> SectionCollapsingHeader<'a> {
/// Create a new [`Self`].
///
/// See also [`crate::UiExt::section_collapsing_header`]
pub fn new(label: impl Into<egui::WidgetText>) -> Self {
Self {
label: label.into(),
default_open: true,
button: None,
help: None,
}
}
/// Set the default open state of the section header.
///
/// Defaults to `true`.
#[inline]
pub fn default_open(mut self, default_open: bool) -> Self {
self.default_open = default_open;
self
}
/// Set the button to be shown in the header.
#[inline]
pub fn button(mut self, button: impl list_item::ItemButton + 'a) -> Self {
self.button = Some(Box::new(button));
self
}
/// Set the help text tooltip to be shown in the header.
//TODO(#6191): the help button should be just another `impl ItemButton`.
#[inline]
pub fn help_text(mut self, help: impl Into<egui::WidgetText>) -> Self {
let help = help.into();
self.help = Some(Box::new(move |ui| {
ui.label(help);
}));
self
}
/// Set the help markdown tooltip to be shown in the header.
//TODO(#6191): the help button should be just another `impl ItemButton`.
#[inline]
pub fn help_markdown(mut self, help: &'a str) -> Self {
self.help = Some(Box::new(move |ui| {
ui.markdown_ui(help);
}));
self
}
/// Set the help UI closure to be shown in the header.
//TODO(#6191): the help button should be just another `impl ItemButton`.
#[inline]
pub fn help_ui(mut self, help: impl FnOnce(&mut egui::Ui) + 'a) -> Self {
self.help = Some(Box::new(help));
self
}
/// Display the header.
pub fn show(
self,
ui: &mut egui::Ui,
add_body: impl FnOnce(&mut egui::Ui),
) -> egui::CollapsingResponse<()> {
let Self {
label,
default_open,
button,
help,
} = self;
let id = ui.make_persistent_id(label.text());
let mut content = list_item::LabelContent::new(label);
if button.is_some() || help.is_some() {
content = content
.with_buttons(|ui| {
let button_response = button.map(|button| button.ui(ui));
let help_response = help.map(|help| ui.help_hover_button().on_hover_ui(help));
match (button_response, help_response) {
(Some(button_response), Some(help_response)) => {
button_response | help_response
}
(Some(response), None) | (None, Some(response)) => response,
(None, None) => unreachable!("at least one of button or help is set"),
}
})
.always_show_buttons(true);
}
let resp = list_item::ListItem::new()
.interactive(true)
.force_background(crate::design_tokens().section_collapsing_header_color())
.show_hierarchical_with_children_unindented(ui, id, default_open, content, |ui| {
//TODO(ab): this space is not desirable when the content actually is list items
ui.add_space(4.0); // Add space only if there is a body to make minimized headers stick together.
add_body(ui);
ui.add_space(4.0); // Same here
});
if resp.item_response.clicked() {
// `show_hierarchical_with_children_unindented` already toggles on double-click,
// but we are _only_ a collapsing header, so we should also toggle on normal click:
if let Some(mut state) = egui::collapsing_header::CollapsingState::load(ui.ctx(), id) {
state.toggle(ui);
state.store(ui.ctx());
}
}
egui::CollapsingResponse {
header_response: resp.item_response,
body_response: resp.body_response.map(|r| r.response),
body_returned: None,
openness: resp.openness,
}
}
}