#![allow(clippy::too_many_arguments)]
use std::{borrow::Cow, ops::DerefMut};
use derive_more::{Deref, DerefMut as DeriveDerefMut, From};
use egui::{Align, Checkbox, Color32, Id, Layout, Response, TextEdit, Ui, UiBuilder, WidgetText};
use facet::{Def, Facet, ListDef, OptionDef, ScalarType, Type, UserType};
use facet_reflect::{
HasFields, Partial, Peek, PeekEnum, PeekListLike, PeekMap, PeekOption, PeekPointer, PeekStruct,
PeekTuple, Poke, PokeEnum, PokeList, PokeStruct,
};
use crate::{
Attr, MaybeMut,
layout::{ProbeHeader, ProbeLayout},
maybe_mut::{Guard, MakeLockErrorKind},
};
fn has_egui_skip(attributes: &[facet::Attr]) -> bool {
attributes
.iter()
.filter_map(|a| a.get_as::<crate::Attr>())
.any(|a| matches!(a, Attr::Skip))
}
fn has_egui_as_display(attributes: &[facet::Attr]) -> bool {
attributes
.iter()
.any(|a| matches!((a.ns, a.key), (Some("egui"), "as_display")))
}
fn egui_rename(attributes: &[facet::Attr]) -> Option<&'static str> {
attributes
.iter()
.filter_map(|a| a.get_as::<crate::Attr>())
.find_map(|a| match a {
Attr::Rename(name) => Some(*name),
_ => None,
})
}
fn field_display_name(field: &facet::Field) -> String {
egui_rename(field.attributes)
.unwrap_or_else(|| field.effective_name())
.to_owned()
}
fn shape_display_name(shape: &facet::Shape) -> &str {
egui_rename(shape.attributes).unwrap_or_else(|| shape.effective_name())
}
fn should_render_as_display(shape: &facet::Shape, attributes: &[facet::Attr]) -> bool {
has_egui_as_display(attributes) || has_egui_as_display(shape.attributes)
}
#[must_use = "use [`FacetProbe::show`] to display the probe in the [`Ui`]"]
#[derive(Deref, DeriveDerefMut)]
pub struct FacetProbe<'mem, 'facet> {
header: Option<WidgetText>,
id: Option<Id>,
read_only: bool,
expand_all: bool,
force_reborrow: bool,
#[deref]
#[deref_mut]
inner: MaybeMut<'mem, 'facet>,
}
#[derive(Debug, From)]
pub enum MaybeMutT<'mem, T> {
Not(&'mem T),
Mut(&'mem mut T),
}
impl<'mem, 'facet> FacetProbe<'mem, 'facet> {
pub fn readonly(self) -> Self {
Self {
read_only: true,
..self
}
}
pub fn expand_all(self) -> Self {
Self {
expand_all: true,
..self
}
}
pub fn with_header(mut self, label: impl Into<WidgetText>) -> Self {
self.header = Some(label.into());
self
}
pub fn with_id_source(mut self, id_source: impl std::hash::Hash) -> Self {
self.id = Some(Id::new(id_source));
self
}
pub unsafe fn force_reborrow(self) -> Self {
Self {
force_reborrow: true,
..self
}
}
pub fn new_peek(value: Peek<'mem, 'facet>) -> Self {
Self {
header: None,
id: None,
read_only: true,
expand_all: false,
force_reborrow: false,
inner: MaybeMut::Not(value),
}
}
pub fn new_poke(value: Poke<'mem, 'facet>) -> Self {
Self {
header: None,
id: None,
read_only: false,
expand_all: false,
force_reborrow: false,
inner: MaybeMut::Mut(value),
}
}
pub fn new<T>(value: impl Into<MaybeMutT<'mem, T>>) -> Self
where
T: Facet<'facet> + 'mem,
{
let v: MaybeMutT<'mem, T> = value.into();
let inner: MaybeMut = match v {
MaybeMutT::Mut(v) => Poke::new(v).into(),
MaybeMutT::Not(v) => Peek::new(v).into(),
};
Self {
header: None,
id: None,
read_only: false,
expand_all: false,
force_reborrow: false,
inner,
}
}
pub fn show<'lock>(self, ui: &mut Ui) -> Response
where
'mem: 'lock,
{
if has_egui_skip(self.shape().attributes) {
return ui.label("");
}
let mut changed = false;
let mut attributes = self
.shape()
.attributes
.iter()
.filter_map(|x| x.get_as::<crate::Attr>());
let readonly = attributes.any(|x| matches!(x, Attr::Readonly)) || self.read_only;
let expand_all = self.expand_all
|| self
.shape()
.attributes
.iter()
.filter_map(|x| x.get_as::<crate::Attr>())
.any(|x| matches!(x, Attr::ExpandAll));
let mut guard: Guard<'lock, 'facet> = if readonly {
let Ok(read) = self.inner.read() else {
return ui.colored_label(Color32::RED, "Read Failure");
};
read
} else {
match self.inner.write() {
Ok(write) => write,
Err(e) if matches!(e.kind, MakeLockErrorKind::NotLockable) => {
let Ok(read) = MaybeMut::Not(e.unchanged).read() else {
return ui.colored_label(Color32::RED, "Fallback Read Failure");
};
read
}
Err(e) if matches!(e.kind, MakeLockErrorKind::LockFailure) => {
return ui.colored_label(Color32::RED, "Lock Failure");
}
Err(e) => {
return ui.colored_label(Color32::RED, format!("Error: {e}"));
}
}
};
let maybe_mut = guard.deref_mut();
let ptr_salt = maybe_mut.as_peek().data().as_byte_ptr() as usize;
let probe_id = self
.id
.unwrap_or_else(|| Id::new(("facet_egui::probe", ptr_salt)));
let mut r = ui
.push_id(probe_id, |ui| {
let child_ui = &mut ui.new_child(
UiBuilder::new()
.max_rect(ui.max_rect())
.layout(Layout::top_down(Align::Min)),
);
let mut layout = ProbeLayout::load(child_ui.ctx(), probe_id.with("layout"));
let root_as_display = should_render_as_display(maybe_mut.shape(), &[]);
if let Some(label) = self.header {
let mut header = show_header(
label,
maybe_mut,
&mut layout,
0,
child_ui,
probe_id.with("root"),
&mut changed,
self.force_reborrow,
expand_all,
root_as_display,
);
if header.openness > 0.0 && !root_as_display {
show_body(
maybe_mut,
&mut header,
&mut layout,
0,
child_ui,
probe_id.with("root"),
&mut changed,
self.force_reborrow,
expand_all,
root_as_display,
);
}
header.store(child_ui.ctx());
} else {
show_body_direct(
maybe_mut,
&mut layout,
0,
child_ui,
probe_id.with("root"),
&mut changed,
self.force_reborrow,
expand_all,
root_as_display,
);
}
layout.store(child_ui.ctx());
let final_rect = child_ui.min_rect();
ui.advance_cursor_after_rect(final_rect);
})
.response;
drop(guard);
if changed {
r.mark_changed();
ui.ctx().request_repaint();
}
r
}
}
fn has_inner(value: &MaybeMut<'_, '_>) -> bool {
let peek = value.as_peek();
if let Ok(opt) = peek.into_option()
&& let Some(inner) = opt.value()
{
return has_inner(&MaybeMut::Not(inner));
}
if let Ok(s) = peek.into_struct() {
return s.field_count() > 0;
}
if let Ok(e) = peek.into_enum()
&& let Ok(v) = e.active_variant()
{
return !v.data.fields.is_empty();
}
if let Ok(l) = peek.into_list_like() {
return !l.is_empty();
}
if let Ok(m) = peek.into_map() {
return !m.is_empty();
}
if let Ok(t) = peek.into_tuple() {
return !t.is_empty();
}
if let Ok(p) = peek.into_pointer()
&& let Some(inner) = p.borrow_inner()
{
return has_inner(&MaybeMut::Not(inner));
}
false
}
#[expect(clippy::too_many_arguments)]
fn show_header(
label: impl Into<WidgetText>,
value: &mut MaybeMut<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
ui: &mut Ui,
id: Id,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
as_display: bool,
) -> ProbeHeader {
let mut header = ProbeHeader::load(ui.ctx(), id);
let row_has_inner = !as_display && has_inner(value);
header.set_has_inner(row_has_inner);
if !row_has_inner {
header.set_open(false);
}
if expand_all && row_has_inner {
header.set_open(true);
}
ui.horizontal(|ui| {
let label_response = layout.inner_label_ui(indent, id.with("label"), ui, |ui| {
if header.has_inner() {
header.collapse_button(ui);
}
ui.label(label)
});
layout.inner_value_ui(id.with("value"), ui, |ui| {
*changed |= show_inline_value(value, ui, id, force_reborrow, as_display)
.labelled_by(label_response.id)
.changed();
});
});
header
}
#[expect(clippy::too_many_arguments)]
fn show_body(
value: &mut MaybeMut<'_, '_>,
header: &mut ProbeHeader,
layout: &mut ProbeLayout,
indent: usize,
ui: &mut Ui,
id: Id,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
as_display: bool,
) {
if as_display {
header.set_has_inner(false);
header.set_open(false);
return;
}
let cursor = ui.cursor();
let table_rect = egui::Rect::from_min_max(
egui::pos2(cursor.min.x, cursor.min.y - header.body_shift()),
ui.max_rect().max,
);
let mut table_ui = ui.new_child(
UiBuilder::new()
.max_rect(table_rect)
.layout(Layout::top_down(Align::Min))
.id_salt(id.with("body")),
);
table_ui.set_clip_rect(
ui.clip_rect()
.intersect(egui::Rect::everything_below(ui.min_rect().max.y)),
);
let got_inner = show_inner_rows(
value,
layout,
indent + 1,
id,
&mut table_ui,
changed,
force_reborrow,
expand_all,
);
header.set_has_inner(got_inner);
let final_table_rect = table_ui.min_rect();
ui.advance_cursor_after_rect(final_table_rect);
let table_height = ui.cursor().min.y - table_rect.min.y;
header.set_body_height(table_height);
}
fn show_body_direct(
value: &mut MaybeMut<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
ui: &mut Ui,
id: Id,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
as_display: bool,
) {
if as_display {
*changed |= show_inline_value(value, ui, id, force_reborrow, true).changed();
return;
}
let cursor = ui.cursor();
let table_rect =
egui::Rect::from_min_max(egui::pos2(cursor.min.x, cursor.min.y), ui.max_rect().max);
let mut table_ui = ui.new_child(
UiBuilder::new()
.max_rect(table_rect)
.layout(Layout::top_down(Align::Min))
.id_salt(id.with("body")),
);
table_ui.set_clip_rect(
ui.clip_rect()
.intersect(egui::Rect::everything_below(ui.min_rect().max.y)),
);
show_inner_rows(
value,
layout,
indent + 1,
id,
&mut table_ui,
changed,
force_reborrow,
expand_all,
);
let final_table_rect = table_ui.min_rect();
ui.advance_cursor_after_rect(final_table_rect);
}
fn show_inner_rows(
value: &mut MaybeMut<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
match value {
MaybeMut::Mut(poke) => show_inner_rows_poke(
poke,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
),
MaybeMut::Not(peek) => show_inner_rows_peek(
*peek,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
),
}
}
fn lock_child<'mem, 'facet>(child: MaybeMut<'mem, 'facet>) -> Option<Guard<'mem, 'facet>> {
match child.write() {
Ok(guard) => Some(guard),
Err(e) if matches!(e.kind, MakeLockErrorKind::NotLockable) => {
MaybeMut::Not(e.unchanged).read().ok()
}
Err(_) => None,
}
}
fn show_inner_rows_poke(
poke: &mut Poke<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
if let Def::Option(option_def) = poke.shape().def {
return show_inner_rows_poke_option(
poke,
option_def,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
if poke.is_enum() {
let enu_poke = match poke.try_reborrow() {
Some(rb) => rb,
None if force_reborrow => unsafe {
Poke::from_raw_parts(poke.data_mut(), poke.shape())
},
None => {
return show_inner_rows_peek(
poke.as_peek(),
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
};
if let Ok(enu) = enu_poke.into_enum() {
return show_inner_rows_poke_enum(
enu,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
return show_inner_rows_peek(
poke.as_peek(),
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
if poke.is_struct() {
let reborrow = match poke.try_reborrow() {
Some(rb) => rb,
None if force_reborrow => unsafe {
Poke::from_raw_parts(poke.data_mut(), poke.shape())
},
None => {
return show_inner_rows_peek(
poke.as_peek(),
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
};
if let Ok(struc) = reborrow.into_struct() {
return show_inner_rows_poke_struct(
struc,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
}
let data_mut = poke.data_mut();
let shape = poke.shape();
let poke = match poke.try_reborrow() {
Some(rb) => rb,
None if force_reborrow => unsafe { Poke::from_raw_parts(poke.data_mut(), poke.shape()) },
None => {
return show_inner_rows_peek(
poke.as_peek(),
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
);
}
};
if let Ok(poke_list) = poke.into_list() {
show_inner_rows_poke_list(
poke_list,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else {
let poke = unsafe { Poke::from_raw_parts(data_mut, shape) };
ui.weak("fallback");
ui.weak("fallback");
show_inner_rows_peek(
poke.as_peek(),
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
}
}
fn show_inner_rows_poke_list(
mut list: PokeList<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let len = list.len();
if len == 0 {
return false;
}
for idx in 0..len {
let label = format!("[{idx}]");
if let Some(field_poke) = list.get_mut(idx) {
let row_id = id.with(("list", idx));
let Some(mut guard) = lock_child(MaybeMut::Mut(field_poke)) else {
continue;
};
let child = &mut *guard;
let as_display = should_render_as_display(child.shape(), &[]);
let mut header = show_header(
&label,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
}
true
}
fn show_inner_rows_poke_struct(
mut struc: PokeStruct<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let count = struc.field_count();
if count == 0 {
return false;
}
let mut got_inner = false;
for idx in 0..count {
let field = &struc.ty().fields[idx];
if has_egui_skip(field.attributes) {
continue;
}
if let Ok(field_poke) = struc.field(idx) {
let row_id = id.with(("struct", idx));
let Some(mut guard) = lock_child(MaybeMut::Mut(field_poke)) else {
continue;
};
let child = &mut *guard;
if field.is_flattened() {
ui.push_id(row_id, |ui| {
got_inner |= show_inner_rows(
child,
layout,
indent,
row_id,
ui,
changed,
force_reborrow,
expand_all,
);
});
continue;
}
got_inner = true;
let field_name = field_display_name(field);
let as_display = should_render_as_display(child.shape(), field.attributes);
let mut header = show_header(
&field_name,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
}
got_inner
}
fn show_inner_rows_poke_enum(
mut enu: PokeEnum<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let variant = match enu.active_variant() {
Ok(v) => v,
Err(_) => return false,
};
let field_count = variant.data.fields.len();
if field_count == 0 {
return false;
}
let mut got_inner = false;
for idx in 0..field_count {
let field = &variant.data.fields[idx];
if has_egui_skip(field.attributes) {
continue;
}
if let Ok(Some(field_poke)) = enu.field(idx) {
let row_id = id.with(("enum", idx));
let Some(mut guard) = lock_child(MaybeMut::Mut(field_poke)) else {
continue;
};
let child = &mut *guard;
if field.is_flattened() {
ui.push_id(row_id, |ui| {
got_inner |= show_inner_rows(
child,
layout,
indent,
row_id,
ui,
changed,
force_reborrow,
expand_all,
);
});
continue;
}
let field_name = field_display_name(field);
let as_display = should_render_as_display(child.shape(), field.attributes);
let mut header = show_header(
&field_name,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
}
got_inner
}
fn show_inner_rows_peek(
peek: Peek<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
if let Ok(opt) = peek.into_option() {
show_inner_rows_peek_option(
opt,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(struc) = peek.into_struct() {
show_inner_rows_peek_struct(
struc,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(enu) = peek.into_enum() {
show_inner_rows_peek_enum(
enu,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(list) = peek.into_list_like() {
show_inner_rows_peek_list(
list,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(map) = peek.into_map() {
show_inner_rows_peek_map(
map,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(tuple) = peek.into_tuple() {
show_inner_rows_peek_tuple(
tuple,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else if let Ok(ptr) = peek.into_pointer() {
show_inner_rows_peek_pointer(
ptr,
layout,
indent,
id,
ui,
changed,
force_reborrow,
expand_all,
)
} else {
false
}
}
fn show_inner_rows_peek_struct(
struc: PeekStruct<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let mut got_inner = false;
for (idx, (field, value)) in struc.fields().enumerate() {
let row_id = id.with(("struct", idx));
if has_egui_skip(field.attributes) {
continue;
}
let Some(mut guard) = lock_child(MaybeMut::Not(value)) else {
continue;
};
let child = &mut *guard;
if field.is_flattened() {
ui.push_id(row_id, |ui| {
got_inner |= show_inner_rows(
child,
layout,
indent,
row_id,
ui,
changed,
force_reborrow,
expand_all,
);
});
continue;
}
got_inner = true;
let field_name = field_display_name(&field);
let as_display = should_render_as_display(child.shape(), field.attributes);
let mut header = show_header(
&field_name,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
got_inner
}
fn show_inner_rows_peek_enum(
enu: PeekEnum<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let mut got_inner = false;
for (idx, (field, value)) in enu.fields().enumerate() {
let row_id = id.with(("enum", idx));
if has_egui_skip(field.attributes) {
continue;
}
let Some(mut guard) = lock_child(MaybeMut::Not(value)) else {
continue;
};
let child = &mut *guard;
if field.is_flattened() {
ui.push_id(row_id, |ui| {
got_inner |= show_inner_rows(
child,
layout,
indent,
row_id,
ui,
changed,
force_reborrow,
expand_all,
);
});
continue;
}
got_inner = true;
let field_name = field_display_name(&field);
let as_display = should_render_as_display(child.shape(), field.attributes);
let mut header = show_header(
&field_name,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
got_inner
}
fn show_inner_rows_peek_list(
list: PeekListLike<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let mut got_inner = false;
for (idx, item) in list.iter().enumerate() {
let row_id = id.with(("list", idx));
got_inner = true;
let label = format!("[{idx}]");
let Some(mut guard) = lock_child(MaybeMut::Not(item)) else {
continue;
};
let child = &mut *guard;
let as_display = should_render_as_display(child.shape(), &[]);
let mut header = show_header(
&label,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
got_inner
}
fn show_inner_rows_peek_map(
map: PeekMap<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let mut got_inner = false;
for (idx, (key, value)) in map.iter().enumerate() {
let row_id = id.with(("map", idx));
got_inner = true;
let label = format!("{}", key);
let Some(mut guard) = lock_child(MaybeMut::Not(value)) else {
continue;
};
let child = &mut *guard;
let as_display = should_render_as_display(child.shape(), &[]);
let mut header = show_header(
&label,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
got_inner
}
fn show_inner_rows_peek_option(
opt: PeekOption<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
if let Some(inner) = opt.value() {
let Some(mut guard) = lock_child(MaybeMut::Not(inner)) else {
return false;
};
let child = &mut *guard;
return show_inner_rows(
child,
layout,
indent,
id.with("option"),
ui,
changed,
force_reborrow,
expand_all,
);
}
false
}
fn show_inner_rows_peek_tuple(
tuple: PeekTuple<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let mut got_inner = false;
for (idx, (_field, value)) in tuple.fields().enumerate() {
let row_id = id.with(("tuple", idx));
got_inner = true;
let label = format!("[{idx}]");
let Some(mut guard) = lock_child(MaybeMut::Not(value)) else {
continue;
};
let child = &mut *guard;
let as_display = should_render_as_display(child.shape(), &[]);
let mut header = show_header(
&label,
child,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
if header.openness > 0.0 && !as_display {
show_body(
child,
&mut header,
layout,
indent,
ui,
row_id,
changed,
force_reborrow,
expand_all,
as_display,
);
}
header.store(ui.ctx());
}
got_inner
}
fn show_inner_rows_peek_pointer(
ptr: PeekPointer<'_, '_>,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
if let Some(inner) = ptr.borrow_inner() {
let Some(mut guard) = lock_child(MaybeMut::Not(inner)) else {
return false;
};
let child = &mut *guard;
return show_inner_rows(
child,
layout,
indent,
id.with("ptr"),
ui,
changed,
force_reborrow,
expand_all,
);
}
false
}
fn show_inline_value(
value: &mut MaybeMut<'_, '_>,
ui: &mut Ui,
id: Id,
force_reborrow: bool,
as_display: bool,
) -> Response {
match value {
MaybeMut::Mut(poke) => show_inline_poke(poke, ui, id, force_reborrow, as_display),
MaybeMut::Not(peek) => show_inline_peek(*peek, ui, id, as_display),
}
}
fn show_inline_poke(
poke: &mut Poke<'_, '_>,
ui: &mut Ui,
id: Id,
force_reborrow: bool,
as_display: bool,
) -> Response {
if as_display || should_render_as_display(poke.shape(), &[]) {
return ui.label(format!("{}", poke.as_peek()));
}
if let Some(scalar_type) = poke.as_peek().scalar_type() {
return show_inline_poke_scalar(poke, scalar_type, ui);
}
if let Def::Option(option_def) = poke.shape().def {
return show_inline_poke_option(poke, option_def, ui, id, force_reborrow);
}
if poke.is_enum() {
return show_inline_poke_enum(poke, ui, id);
}
if poke.is_struct() {
return ui.weak(shape_display_name(poke.shape()));
}
if let Def::List(list_def) = poke.shape().def {
return show_inline_poke_list(poke, list_def, ui);
}
if let Ok(map) = poke.as_peek().into_map() {
return ui.weak(format!("[{}]", map.len()));
}
if let Ok(tuple) = poke.as_peek().into_tuple() {
return ui.weak(format!("({})", tuple.len()));
}
if let Ok(ptr) = poke.as_peek().into_pointer()
&& let Some(inner) = ptr.borrow_inner()
{
return show_inline_peek(inner, ui, id, false);
}
ui.weak(shape_display_name(poke.shape()))
}
fn show_inline_poke_list(poke: &mut Poke<'_, '_>, list_def: ListDef, ui: &mut Ui) -> Response {
let len = poke
.as_peek()
.into_list_like()
.map(|l| l.len())
.unwrap_or(0);
let item_shape = list_def.t();
let has_default = item_shape.is_default();
let has_push = list_def.push().is_some();
let has_set_len = list_def.set_len().is_some();
let mut changed = false;
let r = ui.horizontal(|ui| {
ui.weak(format!("[{len}]"));
if has_push && has_default && ui.small_button("+").clicked() {
changed |= try_push_default_to_list(poke, list_def);
}
if has_set_len && len > 0 && ui.small_button("-").clicked() {
changed |= try_pop_from_list(poke, list_def, item_shape);
}
});
let mut r = r.response;
if changed {
r.mark_changed();
}
r
}
fn try_push_default_to_list(poke: &mut Poke<'_, '_>, list_def: ListDef) -> bool {
let item_shape = list_def.t();
let push_fn = match list_def.push() {
Some(f) => f,
None => return false,
};
let partial = match unsafe { Partial::alloc_shape(item_shape) } {
Ok(p) => p,
Err(_) => return false,
};
let partial = match partial.set_default() {
Ok(p) => p,
Err(_) => return false,
};
let heap_value = match partial.build() {
Ok(v) => v,
Err(_) => return false,
};
unsafe {
let item_ptr = heap_value.peek().data().as_byte_ptr() as *mut u8;
push_fn(poke.data_mut(), facet::PtrMut::new(item_ptr));
}
core::mem::forget(heap_value);
true
}
fn try_pop_from_list(
poke: &mut Poke<'_, '_>,
list_def: ListDef,
item_shape: &'static facet::Shape,
) -> bool {
let set_len_fn = match list_def.set_len() {
Some(f) => f,
None => return false,
};
let len_fn = list_def.vtable.len;
let get_mut_fn = match list_def.vtable.get_mut {
Some(f) => f,
None => return false,
};
let len = unsafe { len_fn(poke.data_mut().as_const()) };
if len == 0 {
return false;
}
let Some(last_ptr) = (unsafe { get_mut_fn(poke.data_mut(), len - 1, poke.shape()) }) else {
return false;
};
unsafe { set_len_fn(poke.data_mut(), len - 1) };
unsafe { item_shape.call_drop_in_place(last_ptr) };
true
}
fn show_inline_poke_option(
poke: &mut Poke<'_, '_>,
option_def: OptionDef,
ui: &mut Ui,
id: Id,
force_reborrow: bool,
) -> Response {
let is_some = unsafe { (option_def.vtable.is_some)(poke.data_mut().as_const()) };
let mut changed = false;
let r = ui.horizontal(|ui| {
if ui.selectable_label(!is_some, "None").clicked() && is_some {
unsafe {
(option_def.vtable.replace_with)(poke.data_mut(), std::ptr::null_mut());
}
changed = true;
}
if ui.selectable_label(is_some, "Some").clicked() && !is_some {
changed = try_set_option_to_some_default(poke, option_def);
}
if is_some {
let inner_ptr = unsafe { (option_def.vtable.get_value)(poke.data_mut().as_const()) };
if !inner_ptr.is_null() {
let mut inner_poke = unsafe {
Poke::from_raw_parts(facet::PtrMut::new(inner_ptr as *mut u8), option_def.t())
};
show_inline_poke(&mut inner_poke, ui, id.with("some"), force_reborrow, false);
}
}
});
let mut r = r.response;
if changed {
r.mark_changed();
}
r
}
fn try_set_option_to_some_default(poke: &mut Poke<'_, '_>, option_def: OptionDef) -> bool {
let inner_shape = option_def.t();
let partial = match unsafe { Partial::alloc_shape(inner_shape) } {
Ok(p) => p,
Err(_) => return false,
};
let partial = match partial.set_default() {
Ok(p) => p,
Err(_) => return false,
};
let heap_value = match partial.build() {
Ok(v) => v,
Err(_) => return false,
};
unsafe {
let value_ptr = heap_value.peek().data().as_byte_ptr() as *mut u8;
(option_def.vtable.replace_with)(poke.data_mut(), value_ptr);
}
core::mem::forget(heap_value);
true
}
fn show_inner_rows_poke_option(
poke: &mut Poke<'_, '_>,
option_def: OptionDef,
layout: &mut ProbeLayout,
indent: usize,
id: Id,
ui: &mut Ui,
changed: &mut bool,
force_reborrow: bool,
expand_all: bool,
) -> bool {
let is_some = unsafe { (option_def.vtable.is_some)(poke.data_mut().as_const()) };
if !is_some {
return false;
}
let inner_ptr = unsafe { (option_def.vtable.get_value)(poke.data_mut().as_const()) };
if inner_ptr.is_null() {
return false;
}
let inner_poke =
unsafe { Poke::from_raw_parts(facet::PtrMut::new(inner_ptr as *mut u8), option_def.t()) };
let mut child = MaybeMut::Mut(inner_poke);
show_inner_rows(
&mut child,
layout,
indent,
id.with("option"),
ui,
changed,
force_reborrow,
expand_all,
)
}
fn show_inline_poke_enum(poke: &mut Poke<'_, '_>, ui: &mut Ui, id: Id) -> Response {
let shape = poke.shape();
let Type::User(UserType::Enum(enum_type)) = shape.ty else {
return ui.weak("enum");
};
let active_name = poke
.as_peek()
.into_enum()
.ok()
.and_then(|e| e.active_variant().ok())
.map(|v| v.effective_name())
.unwrap_or("?");
let mut changed = false;
let r = egui::ComboBox::from_id_salt(id)
.selected_text(active_name)
.show_ui(ui, |ui| {
for (idx, variant) in enum_type.variants.iter().enumerate() {
let variant_name: &str = variant.effective_name();
let is_active = variant_name == active_name;
if ui.selectable_label(is_active, variant_name).clicked()
&& !is_active
&& try_change_variant(poke, idx)
{
changed = true;
}
}
});
let mut r = r.response;
if changed {
r.mark_changed();
}
r
}
fn try_change_variant(poke: &mut Poke<'_, '_>, variant_idx: usize) -> bool {
let shape = poke.shape();
let partial = match unsafe { Partial::alloc_shape(shape) } {
Ok(p) => p,
Err(e) => {
log::debug!("alloc_shape failed: {e}");
return false;
}
};
let mut partial = match partial.select_nth_variant(variant_idx) {
Ok(p) => p,
Err(e) => {
log::debug!("select_nth_variant failed: {e}");
return false;
}
};
let Type::User(UserType::Enum(enum_type)) = shape.ty else {
return false;
};
let variant = &enum_type.variants[variant_idx];
for field_idx in 0..variant.data.fields.len() {
partial = match partial.set_nth_field_to_default(field_idx) {
Ok(p) => p,
Err(e) => {
log::debug!(
"set_nth_field_to_default({field_idx}) failed for variant '{}': {e}",
variant.effective_name()
);
return false;
}
};
}
let heap_value = match partial.build() {
Ok(v) => v,
Err(e) => {
log::debug!("build failed: {e}");
return false;
}
};
let size = shape
.layout
.sized_layout()
.expect("enum must be sized")
.size();
assert_eq!(poke.shape(), heap_value.shape());
unsafe {
let dst = poke.data_mut().as_mut_byte_ptr();
let src = heap_value.peek().data().as_byte_ptr() as *mut u8;
core::ptr::swap_nonoverlapping(dst, src, size);
}
drop(heap_value);
true
}
fn show_inline_poke_scalar(
poke: &mut Poke<'_, '_>,
scalar_type: ScalarType,
ui: &mut Ui,
) -> Response {
match scalar_type {
ScalarType::Bool => {
if let Ok(v) = poke.get_mut::<bool>() {
return ui.add(Checkbox::without_text(v));
}
}
ScalarType::U8 => {
if let Ok(v) = poke.get_mut::<u8>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::U16 => {
if let Ok(v) = poke.get_mut::<u16>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::U32 => {
if let Ok(v) = poke.get_mut::<u32>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::U64 => {
if let Ok(v) = poke.get_mut::<u64>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::U128 => {
if let Ok(v) = poke.get::<u128>() {
return ui.label(format!("{v}"));
}
}
ScalarType::USize => {
if let Ok(v) = poke.get_mut::<usize>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::I8 => {
if let Ok(v) = poke.get_mut::<i8>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::I16 => {
if let Ok(v) = poke.get_mut::<i16>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::I32 => {
if let Ok(v) = poke.get_mut::<i32>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::I64 => {
if let Ok(v) = poke.get_mut::<i64>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::I128 => {
if let Ok(v) = poke.get::<i128>() {
return ui.label(format!("{v}"));
}
}
ScalarType::ISize => {
if let Ok(v) = poke.get_mut::<isize>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::F32 => {
if let Ok(v) = poke.get_mut::<f32>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::F64 => {
if let Ok(v) = poke.get_mut::<f64>() {
return ui.add(egui::DragValue::new(v));
}
}
ScalarType::String => {
if let Ok(v) = poke.get_mut::<String>() {
return ui.add(TextEdit::singleline(v));
}
}
ScalarType::Char => {
if let Ok(v) = poke.get::<char>() {
let s = v.to_string();
return ui.add_enabled(false, TextEdit::singleline(&mut s.as_str()));
}
}
ScalarType::Str => {
if poke.shape().is_display() {
return ui.label(format!("{}", poke.as_peek()));
}
}
ScalarType::CowStr => {
if let Ok(v) = poke.get::<Cow<'_, str>>() {
let mut s = v.clone();
return ui.add_enabled(false, TextEdit::singleline(&mut s));
}
}
_ if poke.shape().is_display() => {
return ui.label(format!("{}", poke.as_peek()));
}
_ if poke.shape().is_debug() => {
return ui.label(format!("{:?}", poke.as_peek()));
}
_ => {}
}
ui.colored_label(
Color32::YELLOW,
format!("unsupported scalar: {scalar_type:?}"),
)
}
fn show_inline_peek(peek: Peek<'_, '_>, ui: &mut Ui, id: Id, as_display: bool) -> Response {
if as_display || should_render_as_display(peek.shape(), &[]) {
return ui.label(format!("{}", peek));
}
if let Some(scalar_type) = peek.scalar_type() {
return show_inline_peek_scalar(peek, scalar_type, ui);
}
if let Ok(opt) = peek.into_option() {
return show_inline_peek_option(opt, ui, id);
}
if let Ok(enu) = peek.into_enum() {
if let Ok(variant) = enu.active_variant() {
return ui.weak(variant.effective_name());
}
return ui.weak("enum");
}
if let Ok(_struc) = peek.into_struct() {
return ui.weak(shape_display_name(peek.shape()));
}
if let Ok(list) = peek.into_list_like() {
return ui.weak(format!("[{}]", list.len()));
}
if let Ok(map) = peek.into_map() {
return ui.weak(format!("[{}]", map.len()));
}
if let Ok(tuple) = peek.into_tuple() {
return ui.weak(format!("({})", tuple.len()));
}
if let Ok(ptr) = peek.into_pointer()
&& let Some(inner) = ptr.borrow_inner()
{
return show_inline_peek(inner, ui, id.with("ptr"), false);
}
ui.weak(shape_display_name(peek.shape()))
}
fn show_inline_peek_scalar(peek: Peek<'_, '_>, scalar_type: ScalarType, ui: &mut Ui) -> Response {
match scalar_type {
ScalarType::Bool => {
if let Ok(v) = peek.get::<bool>() {
let mut value = *v;
return ui.add_enabled(false, Checkbox::without_text(&mut value));
}
}
ScalarType::Char => {
if let Ok(c) = peek.get::<char>() {
let s = c.to_string();
return ui.add_enabled(false, TextEdit::singleline(&mut s.as_str()));
}
}
ScalarType::Str => {
if let Ok(v) = peek.get::<str>() {
return ui.add_enabled(false, TextEdit::singleline(&mut &*v));
}
}
ScalarType::CowStr => {
if let Ok(v) = peek.get::<Cow<'_, str>>() {
let mut s = v.clone();
return ui.add_enabled(false, TextEdit::singleline(&mut s));
}
}
ScalarType::String => {
if let Ok(v) = peek.get::<String>() {
let mut s: Cow<'_, str> = Cow::Borrowed(v.as_str());
return ui.add_enabled(false, TextEdit::singleline(&mut s));
}
}
_ if peek.shape().is_display() => {
return ui.label(format!("{}", peek));
}
_ if peek.shape().is_debug() => {
return ui.label(format!("{:?}", peek));
}
_ => {}
}
ui.colored_label(
Color32::YELLOW,
format!("unsupported scalar: {scalar_type:?}"),
)
}
fn show_inline_peek_option(opt: PeekOption<'_, '_>, ui: &mut Ui, id: Id) -> Response {
ui.horizontal(|ui| {
let is_some = opt.value().is_some();
let _ = ui.selectable_label(!is_some, "None");
let _ = ui.selectable_label(is_some, "Some");
if let Some(inner) = opt.value() {
show_inline_peek(inner, ui, id.with("some"), false);
}
})
.response
}