#![allow(unused)]
use egui::{
AsId, Id, Response, Sense, Ui, WidgetText,
collapsing_header::{CollapsingState, IconPainter, paint_default_icon},
};
use std::hash::Hash;
pub struct CollapsingEnumVariantEditor {
text: WidgetText,
tooltip: WidgetText,
label_ratio: f32,
default_open: bool,
open: Option<bool>,
id_salt: Id,
enabled: bool,
selectable: bool,
selected: bool,
show_background: bool,
icon: Option<IconPainter>,
}
impl CollapsingEnumVariantEditor {
pub fn new(
text: impl Into<WidgetText>,
tooltip: impl Into<WidgetText>,
label_ratio: f32,
) -> Self {
let text = text.into();
let tooltip = tooltip.into();
let id_salt = Id::new(text.text());
Self {
text,
tooltip,
label_ratio,
default_open: false,
open: None,
id_salt,
enabled: true,
selectable: false,
selected: false,
show_background: false,
icon: None,
}
}
#[inline]
pub fn default_open(mut self, open: bool) -> Self {
self.default_open = open;
self
}
#[inline]
pub fn open(mut self, open: Option<bool>) -> Self {
self.open = open;
self
}
#[inline]
pub fn id_salt(mut self, id_salt: impl AsId) -> Self {
self.id_salt = Id::new(id_salt);
self
}
#[deprecated = "Renamed id_salt"]
#[inline]
pub fn id_source(mut self, id_salt: impl AsId) -> Self {
self.id_salt = Id::new(id_salt);
self
}
#[inline]
pub fn enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
#[inline]
pub fn show_background(mut self, show_background: bool) -> Self {
self.show_background = show_background;
self
}
#[inline]
pub fn icon(mut self, icon_fn: impl FnOnce(&mut Ui, f32, &Response) + 'static) -> Self {
self.icon = Some(Box::new(icon_fn));
self
}
}
struct Prepared {
header_response: Response,
combo_response: Option<Response>,
state: CollapsingState,
openness: f32,
}
impl CollapsingEnumVariantEditor {
#[inline]
pub fn show<R, Obj>(
self,
ui: &mut Ui,
edited_obj: &mut Obj,
selected_text: impl Into<WidgetText>,
combo_content: impl FnOnce(&mut Ui, &mut Obj) -> Response,
add_body: impl FnOnce(&mut Ui, &mut Obj) -> R,
indented: bool,
) -> CollapsingComboBoxResponse<R> {
ui.vertical(|ui| {
if !self.enabled {
ui.disable();
}
let Prepared {
header_response,
combo_response,
mut state,
openness,
} = self.begin(ui, edited_obj, selected_text, combo_content);
let ret_response = if indented {
state.show_body_indented(&header_response, ui, |ui| add_body(ui, edited_obj))
} else {
state.show_body_unindented(ui, |ui| add_body(ui, edited_obj))
};
if let Some(ret_response) = ret_response {
CollapsingComboBoxResponse {
header_response,
combo_response,
body_response: Some(ret_response.response),
body_returned: Some(ret_response.inner),
openness,
}
} else {
CollapsingComboBoxResponse {
header_response,
combo_response,
body_response: None,
body_returned: None,
openness,
}
}
})
.inner
}
fn begin<Obj>(
self,
ui: &mut Ui,
edited_obj: &mut Obj,
selected_text: impl Into<WidgetText>,
combo_content: impl FnOnce(&mut Ui, &mut Obj) -> Response,
) -> Prepared {
let Self {
label_ratio,
icon,
text,
default_open,
open,
id_salt,
enabled: _,
selectable: _,
selected: _,
show_background: _,
tooltip,
} = self;
let id = ui.make_persistent_id(id_salt);
let mut state = CollapsingState::load_with_default_open(ui.ctx(), id, default_open);
let (mut header_response, combo_response) = ui
.horizontal(|ui| {
let total_width = ui.available_width();
let spacing = ui.spacing().item_spacing.x;
let label_width = total_width * label_ratio;
let combo_width = total_width - label_width - spacing;
let (rect, _) = ui.allocate_exact_size(
egui::vec2(label_width, ui.spacing().interact_size.y),
Sense::click(),
);
let mut child_ui = ui.new_child(
egui::UiBuilder::new()
.max_rect(rect)
.layout(egui::Layout::left_to_right(egui::Align::LEFT)),
);
let (icon_rect, _) = ui.spacing().icon_rectangles(rect);
let (_rect, icon_resp) =
child_ui.allocate_exact_size(icon_rect.size(), Sense::click());
let openness = state.openness(ui.ctx());
if let Some(icon) = icon {
icon(&mut child_ui, openness, &icon_resp)
} else {
paint_default_icon(ui, openness, &icon_resp)
}
let mut label_response = child_ui
.add(
egui::Label::new(text.clone())
.truncate()
.halign(egui::Align::Min)
.show_tooltip_when_elided(true),
)
.union(icon_resp);
if !tooltip.is_empty() {
if self.enabled {
label_response = label_response.on_hover_text(tooltip);
} else {
label_response = label_response.on_disabled_hover_text(tooltip);
}
}
let mut combo_response = ui
.allocate_ui_with_layout(
egui::vec2(combo_width, ui.spacing().interact_size.y),
egui::Layout::right_to_left(egui::Align::Center),
|ui| {
egui::ComboBox::from_id_salt(id.with("combo"))
.selected_text(selected_text)
.width(combo_width)
.truncate()
.show_ui(ui, |ui| combo_content(ui, edited_obj))
.inner
},
)
.inner;
(label_response, combo_response)
})
.inner;
if let Some(open) = open {
if open != state.is_open() {
state.toggle(ui);
header_response.mark_changed();
}
} else if header_response.clicked() {
state.toggle(ui);
header_response.mark_changed();
}
let openness = state.openness(ui.ctx());
Prepared {
header_response,
combo_response,
state,
openness,
}
}
#[inline]
pub fn show_unindented<R, Obj>(
self,
ui: &mut Ui,
edited_obj: &mut Obj,
selected_text: &String,
combo_content: impl FnOnce(&mut Ui, &mut Obj) -> Response,
add_body: impl FnOnce(&mut Ui, &mut Obj) -> R,
) -> CollapsingComboBoxResponse<R> {
self.show(
ui,
edited_obj,
selected_text,
combo_content,
add_body,
false,
)
}
}
pub struct CollapsingComboBoxResponse<R> {
pub header_response: Response,
pub combo_response: Option<Response>,
pub body_response: Option<Response>,
pub body_returned: Option<R>,
pub openness: f32,
}
impl<R> CollapsingComboBoxResponse<R> {
pub fn fully_closed(&self) -> bool {
self.openness <= 0.0
}
pub fn fully_open(&self) -> bool {
self.openness >= 1.0
}
}