use std::hash::Hash;
use super::super::icons::{paint_editor_icon, EditorIcon};
use super::{button, ButtonProps, ElementStyle};
pub struct ComboBoxProps<'a> {
pub id_salt: egui::Id,
pub selected_text: &'a str,
pub tooltip: Option<&'a str>,
pub enabled: bool,
pub style: ElementStyle,
pub popup_min_width: f32,
pub leading_icon: Option<EditorIcon>,
}
impl<'a> ComboBoxProps<'a> {
#[must_use]
pub fn new(id_salt: impl Hash, selected_text: &'a str) -> Self {
Self {
id_salt: egui::Id::new(id_salt),
selected_text,
tooltip: None,
enabled: true,
style: ElementStyle::default(),
popup_min_width: 0.0,
leading_icon: None,
}
}
}
pub struct ComboBoxResponse<R> {
pub response: egui::Response,
pub inner: Option<R>,
}
pub fn combo_box<R>(
ui: &mut egui::Ui,
props: ComboBoxProps<'_>,
add_contents: impl FnOnce(&mut egui::Ui) -> R,
) -> ComboBoxResponse<R> {
let ComboBoxProps {
id_salt,
selected_text,
tooltip,
enabled,
style,
popup_min_width,
leading_icon,
} = props;
let popup_id = ui.make_persistent_id(id_salt).with("popup");
let open = ui.memory(|memory| memory.is_popup_open(popup_id));
let mut button_style = style;
if leading_icon.is_some() {
button_style.padding.left += 18.0;
}
button_style.padding.right += 14.0;
let response = button(
ui,
ButtonProps {
text: selected_text,
tooltip,
enabled,
style: button_style,
},
);
let arrow_rect = egui::Rect::from_center_size(
egui::pos2(response.rect.right() - 10.0, response.rect.center().y),
egui::vec2(12.0, 12.0),
);
paint_editor_icon(
ui.painter(),
if open {
EditorIcon::ChevronUp
} else {
EditorIcon::ChevronDown
},
arrow_rect,
ui.style().interact(&response).text_color(),
);
if let Some(icon) = leading_icon {
let icon_rect = egui::Rect::from_center_size(
egui::pos2(response.rect.left() + 12.0, response.rect.center().y),
egui::vec2(14.0, 14.0),
);
paint_editor_icon(
ui.painter(),
icon,
icon_rect,
ui.style().interact(&response).text_color(),
);
}
if enabled && response.clicked() {
ui.memory_mut(|memory| memory.toggle_popup(popup_id));
}
let inner = egui::popup::popup_below_widget(
ui,
popup_id,
&response,
egui::popup::PopupCloseBehavior::CloseOnClick,
|ui| {
ui.set_min_width(popup_min_width.max(response.rect.width()));
add_contents(ui)
},
);
ComboBoxResponse { response, inner }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::editor::gui_elements::EditorTheme;
#[test]
fn fixed_combo_box_uses_the_requested_control_size() {
let context = egui::Context::default();
let mut control_rect = None;
let _ = context.run(egui::RawInput::default(), |context| {
egui::CentralPanel::default().show(context, |ui| {
control_rect = Some(
combo_box(
ui,
ComboBoxProps {
style: ElementStyle::fixed(76.0, 28.0),
..ComboBoxProps::new("profile", "Debug")
},
|_| {},
)
.response
.rect,
);
});
});
assert_eq!(control_rect.unwrap().size(), egui::vec2(76.0, 28.0));
}
#[test]
fn toolbar_combo_box_aligns_with_toolbar_button() {
let context = egui::Context::default();
let mut control_rects = None;
let _ = context.run(egui::RawInput::default(), |context| {
egui::CentralPanel::default().show(context, |ui| {
ui.horizontal(|ui| {
let button =
EditorTheme::toolbar_button(ui, "Play", false, true);
let combo = EditorTheme::toolbar_combo_box(
ui,
"profile",
"Debug",
90.0,
|_| {},
);
control_rects = Some((button.rect, combo.response.rect));
});
});
});
let (button, combo) = control_rects.unwrap();
assert_eq!(button.height(), combo.height());
assert_eq!(button.top(), combo.top());
assert_eq!(button.bottom(), combo.bottom());
}
#[test]
fn repeated_local_salts_are_unique_inside_different_ui_scopes() {
let context = egui::Context::default();
let mut popup_ids = Vec::new();
let _ = context.run(egui::RawInput::default(), |context| {
egui::CentralPanel::default().show(context, |ui| {
for area_id in [1_u64, 2_u64] {
ui.push_id(("dock_area", area_id), |ui| {
popup_ids.push(
ui.make_persistent_id(egui::Id::new("selector"))
.with("popup"),
);
combo_box(
ui,
ComboBoxProps::new("selector", "Scene"),
|_| {},
);
});
}
});
});
assert_ne!(popup_ids[0], popup_ids[1]);
}
}