use std::any::{Any, TypeId}; use std::borrow::Cow; use std::io::Cursor; use crate::ecs::actions::Actions; use crate::ecs::component::local::Local; use crate::ecs::component::{Info as ComponentInfo, Parts as ComponentParts}; use crate::ecs::entity::{Handle as EntityHandle, Name as EntityName}; use crate::ecs::sole::Single; use crate::ecs::uid::Uid; use crate::ecs::util::Either; use crate::ecs::{Component, Query, World}; use crate::image::{Format as ImageFormat, Image}; use crate::reflection::{ Enum as EnumReflection, LiteralType, Type as TypeReflection, Visibility, }; use crate::ui::dear_imgui::{ bindings as dear_imgui_bindings, Context as DearImguiContext, }; const BUTTON_RED_NORMAL: [f32; 4] = [124.0, 52.0, 39.0, 255.0]; const BUTTON_RED_HOVERED: [f32; 4] = [181.0, 76.0, 57.0, 255.0]; const BUTTON_RED_ACTIVE: [f32; 4] = [162.0, 68.0, 51.0, 255.0]; #[derive(Component)] pub struct State { spawning_entity: Option, add_component_popup_state: Option, despawn_icon_tex: Option, } impl Default for State { fn default() -> Self { Self { spawning_entity: None, add_component_popup_state: None, despawn_icon_tex: None, } } } #[derive(Debug)] pub struct AddComponentPopupState { popup_name: String, target_entity_id: Uid, search_text: String, searched_components: Vec<(Uid, ComponentInfo, ComponentUserCreatable)>, searchable_components: Vec<(Uid, ComponentInfo, ComponentUserCreatable)>, selected_search_result: i32, new_component: Option<(Box, ComponentInfo, Uid)>, } pub fn show( query: Query<()>, mut dear_imgui_context: Single, mut state: Local, world: &World, mut actions: Actions, ) -> Result<(), crate::Error> { let dear_imgui_context = dear_imgui_context.get_mut()?; let State { spawning_entity, add_component_popup_state, despawn_icon_tex, } = &mut *state; let despawn_icon_tex = despawn_icon_tex .get_or_insert_with(|| create_despawn_icon_texture(dear_imgui_context)); let Some(frame) = dear_imgui_context.frame() else { return Ok(()); }; frame .window("World") .size([600.0, 400.0], dear_imgui_bindings::Condition::Once) .build(|| { create_spawn_button_widgets( frame, spawning_entity, add_component_popup_state, world, &mut actions, ); frame.spacing(); frame.spacing(); for ent_handle in &query.into_flexible_query() { create_entity_widgets( frame, add_component_popup_state, despawn_icon_tex, &ent_handle, world, &mut actions, ); } if let Some(popup_state) = add_component_popup_state { if show_add_component_popup(frame, popup_state, &mut actions) { *add_component_popup_state = None; } } }); Ok(()) } fn create_despawn_icon_texture( dear_imgui_context: &mut DearImguiContext, ) -> dear_imgui_bindings::OwnedTextureData { let despawn_icon_image = Image::from_reader( Cursor::new(include_bytes!("../../../res/ui/delete.png")), ImageFormat::Png, ) .unwrap() .into_rgba8(); let mut despawn_icon_tex = dear_imgui_bindings::OwnedTextureData::new(); despawn_icon_tex.create( dear_imgui_bindings::TextureFormat::RGBA32, despawn_icon_image.dimensions().width, despawn_icon_image.dimensions().height, ); despawn_icon_tex.set_data(despawn_icon_image.as_bytes()); despawn_icon_tex.set_status(dear_imgui_bindings::TextureStatus::WantCreate); dear_imgui_context.register_texture(&mut despawn_icon_tex); despawn_icon_tex } fn create_spawn_button_widgets( frame: &dear_imgui_bindings::Ui, spawning_entity: &mut Option, add_component_popup_state: &mut Option, world: &World, actions: &mut Actions, ) { let _disabled_token = frame.begin_disabled_with_cond(spawning_entity.is_some()); if frame.button("Spawn") { let new_ent_id = actions.spawn(()); *spawning_entity = Some(new_ent_id); } if let Some(spawning_ent) = &spawning_entity { if let Some(spawned_ent) = world.get_entity(*spawning_ent) { *spawning_entity = None; let spawned_ent_title = create_entity_title(&spawned_ent); enter_add_component_popup_state( add_component_popup_state, &spawned_ent, &spawned_ent_title, world, ); } } } fn create_entity_widgets( frame: &dear_imgui_bindings::Ui, add_component_popup_state: &mut Option, despawn_icon_tex: &mut dear_imgui_bindings::OwnedTextureData, ent_handle: &EntityHandle, world: &World, actions: &mut Actions, ) { if ent_handle.component_ids().next().is_some() && !ent_handle.component_ids().any(|comp_id| { world.get_entity(comp_id).is_some_and(|comp_ent| { comp_ent .get::() .is_some_and(|comp_info| comp_info.type_reflection.is_some()) }) }) { return; } let ent_title = create_entity_title(&ent_handle); let mut is_open = false; frame .table(&format!("ent_{}_table", ent_handle.uid())) .headers(false) .sizing_policy(dear_imgui_bindings::TableSizingPolicy::FixedFit) .columns([ dear_imgui_bindings::TableColumnSetup::new("a") .fixed_width(frame.content_region_avail_width() * 0.8), dear_imgui_bindings::TableColumnSetup::new("b"), dear_imgui_bindings::TableColumnSetup::new("c"), ]) .build(|frame| { frame.table_next_row(); frame.table_next_column(); is_open = frame .collapsing_header(&ent_title, dear_imgui_bindings::TreeNodeFlags::NONE); frame.table_next_column(); create_add_component_button_widget( frame, &ent_handle, &ent_title, add_component_popup_state, world, ); frame.table_next_column(); create_despawn_button_widget(frame, despawn_icon_tex, ent_handle, actions); }); if is_open { for component_id in ent_handle.component_ids() { create_component_widgets(frame, ent_handle, component_id, world, actions); } } } fn create_component_widgets( frame: &dear_imgui_bindings::Ui, ent_handle: &EntityHandle, component_id: Uid, world: &World, actions: &mut Actions, ) { let Some(component_info) = world .get_entity(component_id) .and_then(|comp_ent| comp_ent.get::()) else { return; }; let Some(component_type) = component_info.type_reflection else { return; }; let Some(mut component) = ent_handle.get_any_by_id_mut(component_id) else { unreachable!(); }; { let _color_token = frame.push_style_color( dear_imgui_bindings::StyleColor::Button, BUTTON_RED_NORMAL.map(|num| num / 255.0), ); let _color_token_b = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonHovered, BUTTON_RED_HOVERED.map(|num| num / 255.0), ); let _color_token_c = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonActive, BUTTON_RED_ACTIVE.map(|num| num / 255.0), ); if frame.small_button(format!( "X##world_view_entity_{}_remove_component_button_{}", ent_handle.uid(), component_info.name )) { actions.remove_components(ent_handle.uid(), [component_id]); } } frame.same_line(); let mut component_changed = false; add_item_to_frame( frame, ItemRef::Mutable(&mut *component), &mut component_changed, ItemInfo { item_title: component_info.name, item_tag: &format!( "world_view_entity_{}_component_{}", ent_handle.uid(), component_info.name, ), item_type_name: None, item_type: ItemType::Reflected(component_type), item_is_read_only: false, }, &[], ); frame.spacing(); if component_changed { component.set_changed(); } } fn create_add_component_button_widget( frame: &dear_imgui_bindings::Ui, ent_handle: &EntityHandle, ent_title: &str, add_component_popup_state: &mut Option, world: &World, ) { let _color_token = frame.push_style_color( dear_imgui_bindings::StyleColor::Button, [49.0, 89.0, 28.0, 255.0].map(|num| num / 255.0), ); let _color_token_b = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonHovered, [100.0, 181.0, 57.0, 255.0].map(|num| num / 255.0), ); let _color_token_c = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonActive, [90.0, 162.0, 51.0, 255.0].map(|num| num / 255.0), ); let is_button_clicked = frame.button(format!( "+##add_component_to_entity_button_{}", ent_handle.uid() )); if add_component_popup_state .as_ref() .is_some_and(|add_component_popup_state| { add_component_popup_state.target_entity_id != ent_handle.uid() }) { return; } if is_button_clicked { enter_add_component_popup_state( add_component_popup_state, ent_handle, ent_title, world, ); } } fn create_despawn_button_widget( frame: &dear_imgui_bindings::Ui, despawn_icon_tex: &mut dear_imgui_bindings::OwnedTextureData, ent_handle: &EntityHandle, actions: &mut Actions, ) { let _color_token = frame.push_style_color( dear_imgui_bindings::StyleColor::Button, BUTTON_RED_NORMAL.map(|num| num / 255.0), ); let _color_token_b = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonHovered, BUTTON_RED_HOVERED.map(|num| num / 255.0), ); let _color_token_c = frame.push_style_color( dear_imgui_bindings::StyleColor::ButtonActive, BUTTON_RED_ACTIVE.map(|num| num / 255.0), ); if frame .image_button_config( &format!("ent_{}_despawn_button", ent_handle.uid()), despawn_icon_tex.as_mut(), [14.0, 14.0], ) .build() { actions.despawn(ent_handle.uid()); } } fn enter_add_component_popup_state( add_component_popup_state: &mut Option, ent_handle: &EntityHandle, ent_title: &str, world: &World, ) { let popup_name = format!("Add component to {ent_title}"); let mut searchable_components = world .query::<(&ComponentInfo,), ()>() .iter_with_euids() .map(|(id, (component_info,))| { let user_creatable = if component_info.type_reflection.is_none() { ComponentUserCreatable::No { reason: "no type reflection" } } else if component_info .type_reflection .is_some_and(|ty| !ty.has_default_value()) { ComponentUserCreatable::No { reason: "no default value" } } else if !get_whole_type_is_user_editable( component_info.type_reflection, component_info.ty_id, ) { ComponentUserCreatable::No { reason: "whole component is not user editable", } } else { ComponentUserCreatable::Yes }; (id, component_info.clone(), user_creatable) }) .collect::>(); searchable_components.sort_by_key(|(_, _, user_creatable)| *user_creatable); *add_component_popup_state = Some(AddComponentPopupState { popup_name: popup_name.clone(), target_entity_id: ent_handle.uid(), search_text: String::with_capacity(20), searched_components: searchable_components.clone(), searchable_components, selected_search_result: -1, new_component: None, }); } fn show_add_component_popup( frame: &dear_imgui_bindings::Ui, add_component_popup_state: &mut AddComponentPopupState, actions: &mut Actions, ) -> bool { frame.open_popup(&add_component_popup_state.popup_name); let mut is_opened = true; let should_close = frame.modal_popup_with_opened( &add_component_popup_state.popup_name, &mut is_opened, || { frame.set_window_size([700.0, 600.0]); frame.spacing(); frame.spacing(); frame.text("Search:"); frame.same_line(); if frame .input_text( "##add_component_popup_search_input", &mut add_component_popup_state.search_text, ) .build() { add_component_popup_state.searched_components = add_component_popup_state .searchable_components .iter() .cloned() .filter(|(_, searchable_comp_info, _)| { searchable_comp_info .name .contains(&add_component_popup_state.search_text) }) .collect(); add_component_popup_state.selected_search_result = -1; } frame.spacing(); frame.spacing(); let _item_width_token = frame.push_item_width(-1.0); if frame .list_box_config("##add_component_popup_search_results") .build_extended( frame, &mut add_component_popup_state.selected_search_result, &add_component_popup_state.searched_components, &|(_, comp_info, comp_user_creatable)| ListBoxItem { label: comp_info.name.into(), disabled: matches!( comp_user_creatable, ComponentUserCreatable::No { .. } ), hover_tooltip: match comp_user_creatable { ComponentUserCreatable::Yes => None, ComponentUserCreatable::No { reason } => Some( format!("Component cannot be selected: {reason}").into(), ), }, }, ) { let (selected_comp_id, selected_comp_info, _) = add_component_popup_state .searched_components [add_component_popup_state.selected_search_result as usize] .clone(); let Some(selected_component_type) = selected_comp_info.type_reflection else { unreachable!(); }; let Some(selected_comp_default_value) = selected_component_type.default_value() else { unreachable!(); // Only components with default values are selectable }; add_component_popup_state.new_component = Some(( selected_comp_default_value, selected_comp_info, selected_comp_id, )); } frame.spacing(); frame.spacing(); frame.spacing(); if let Some((new_component, new_component_info, _)) = add_component_popup_state.new_component.as_mut() { let mut component_changed = false; let Some(new_component_type) = new_component_info.type_reflection else { unreachable!(); }; add_item_to_frame( frame, ItemRef::Mutable(&mut **new_component), &mut component_changed, ItemInfo { item_title: new_component_info.name, item_tag: &format!( "add_component_popup_component_{}", new_component_info.name, ), item_type_name: None, item_type: ItemType::Reflected(new_component_type), item_is_read_only: false, }, &[], ); frame.spacing(); frame.spacing(); frame.spacing(); } let _disabled = frame.begin_disabled_with_cond( add_component_popup_state.new_component.is_none(), ); let add_text_size = frame.current_font().calc_text_size( frame.clone_style().font_size_base(), frame.content_region_avail_width(), 0.0, "Add", ); if frame.button_with_size( "Add##add_component_popup_add_button", [add_text_size[0] * 2.0, add_text_size[1] * 2.0], ) { if let Some((new_component_data, new_component_info, new_component_id)) = add_component_popup_state.new_component.take() { actions.add_components( add_component_popup_state.target_entity_id, [ComponentParts::builder() .name(new_component_info.name) .type_reflection(new_component_info.type_reflection) .build_with_any_data(new_component_id, new_component_data)], ); return true; } } false }, ); if let Some(true) = should_close { return true; } if !is_opened { return true; } false } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum ComponentUserCreatable { Yes, No { reason: &'static str, }, } struct ItemInfo<'a> { item_title: &'a str, item_tag: &'a str, item_type_name: Option<&'static str>, item_type: ItemType, item_is_read_only: bool, } enum ItemType { Reflected(&'static TypeReflection), String, CowStr, } fn get_item_type(ty: Option<&'static TypeReflection>, type_id: TypeId) -> Option { if let Some(ty) = ty { Some(ItemType::Reflected(ty)) } else if type_id == TypeId::of::() { Some(ItemType::String) } else if type_id == TypeId::of::>() { Some(ItemType::CowStr) } else { None } } enum ItemRef<'item> { Mutable(&'item mut dyn Any), Immutable(&'item dyn Any), } impl AsRef for ItemRef<'_> { fn as_ref(&self) -> &dyn Any { match self { Self::Mutable(item) => &**item, Self::Immutable(item) => *item, } } } fn add_item_to_frame<'a>( frame: &dear_imgui_bindings::Ui, mut item: ItemRef<'_>, data_changed: &mut bool, ItemInfo { item_title, item_tag, item_type_name, item_type, item_is_read_only, }: ItemInfo<'a>, prev_item_tags: &[&'a str], ) { if !matches!(item_type, ItemType::Reflected(TypeReflection::Reference(_))) { frame.text(&item_title); if let Some(item_type_name) = item_type_name { if frame.is_item_hovered() { frame.tooltip_text(item_type_name); } } } let _disabled = frame.begin_disabled_with_cond( !matches!( item_type, ItemType::Reflected( TypeReflection::Struct(_) | TypeReflection::Enum(EnumReflection { is_unit_only: false, .. }) | TypeReflection::Array(_) | TypeReflection::Slice(_) ) ) && item_is_read_only, ); match item_type { ItemType::Reflected(TypeReflection::Struct(struct_reflection)) => { frame.indent(); let mut prev_item_tags = prev_item_tags.to_vec(); prev_item_tags.push(item_tag.as_ref()); for (field_index, field) in struct_reflection.fields.iter().enumerate() { let field_name = field .name .map(Cow::Borrowed) .unwrap_or_else(|| field_index.to_string().into()); let Some(field_item_type) = get_item_type(field.type_reflection(), field.type_id) else { continue; }; let Ok(field_item) = (match &mut item { ItemRef::Mutable(item) => { (struct_reflection.try_get_field_mut)(*item, field_index) .map(ItemRef::Mutable) } ItemRef::Immutable(item) => { (struct_reflection.try_get_field)(*item, field_index) .map(ItemRef::Immutable) } }) else { unreachable!(); }; add_item_to_frame( frame, field_item, data_changed, ItemInfo { item_title: field_name.as_ref(), item_tag: field_name.as_ref(), item_type_name: Some(field.type_name()), item_type: field_item_type, item_is_read_only: item_is_read_only || matches!( field.visibility, Visibility::Private | Visibility::PubScoped(_) ), }, &prev_item_tags, ); } frame.unindent(); } ItemType::Reflected(TypeReflection::Enum(enum_type)) => { if enum_type.is_unit_only { frame.same_line(); } else { frame.indent(); } create_enum_widgets( frame, &mut item, data_changed, item_tag, item_is_read_only, enum_type, prev_item_tags, ); if !enum_type.is_unit_only { frame.unindent(); } } ItemType::Reflected(TypeReflection::Literal(literal_reflection)) => { frame.same_line(); match literal_reflection.ty { LiteralType::U8 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::I8 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::U16 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::I16 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::U32 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::I32 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::U64 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::I64 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::F32 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::F64 => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::Usize => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::Isize => create_scalar_item_input::( frame, &mut item, data_changed, &item_tag, &prev_item_tags, ), LiteralType::U128 => { let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::() else { unreachable!(); }; &mut item.clone() } }; frame.text(item.to_string()); } LiteralType::I128 => { let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::() else { unreachable!(); }; &mut item.clone() } }; frame.text(item.to_string()); } LiteralType::Bool => { let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::() else { unreachable!(); }; &mut item.clone() } }; if frame.checkbox( create_item_label(&item_tag, "value_input", &prev_item_tags), item, ) { *data_changed = true; } } LiteralType::Str => { let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_ref::<&'static str>() else { unreachable!(); }; *item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::<&'static str>() else { unreachable!(); }; *item } }; frame.text(item); } _ => unimplemented!(), } } ItemType::Reflected(TypeReflection::Array(array_type)) => { frame.indent(); let mut prev_item_tags = prev_item_tags.to_vec(); prev_item_tags.push(item_tag); for array_item_index in 0..array_type.length { let array_item_name = array_item_index.to_string(); let Ok(item_item) = (match &mut item { ItemRef::Mutable(item) => { (array_type.try_get_item_mut)(*item, array_item_index) .map(ItemRef::Mutable) } ItemRef::Immutable(item) => { (array_type.try_get_item)(*item, array_item_index) .map(ItemRef::Immutable) } }) else { unreachable!(); }; add_item_to_frame( frame, item_item, data_changed, ItemInfo { item_title: array_item_name.as_ref(), item_tag: array_item_name.as_ref(), item_type_name: Some(array_type.item_type_name()), item_type: ItemType::Reflected(array_type.item_type), item_is_read_only, }, &prev_item_tags, ); } frame.unindent(); } ItemType::Reflected(TypeReflection::Slice(slice_type)) => { frame.indent(); let mut prev_item_tags = prev_item_tags.to_vec(); prev_item_tags.push(item_tag); let Some(len) = (slice_type.try_get_len)(item.as_ref()) else { unreachable!(); }; for index in 0..len { let item_name = index.to_string(); let Ok(item_item) = (slice_type.try_get_item)(item.as_ref(), index) else { unreachable!(); }; let mut slice_item_changed = false; add_item_to_frame( frame, ItemRef::Immutable(item_item), &mut slice_item_changed, ItemInfo { item_title: &item_name, item_tag: &item_name, item_type_name: Some(slice_type.item_type_name()), item_type: ItemType::Reflected(slice_type.item_type), item_is_read_only: true, }, &prev_item_tags, ); assert!(!slice_item_changed); } frame.unindent(); } ItemType::Reflected(TypeReflection::Reference(ref_type)) => { let mut derefed_changed = false; let Some(derefed_item) = (ref_type.try_deref)(item.as_ref()) else { unreachable!(); }; add_item_to_frame( frame, ItemRef::Immutable(derefed_item), &mut derefed_changed, ItemInfo { item_title, item_tag: "derefed".into(), item_type_name, item_type: ItemType::Reflected(ref_type.ty), item_is_read_only: true, }, prev_item_tags, ); assert!(!derefed_changed); } ItemType::String => { frame.same_line(); let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::() else { unreachable!(); }; &mut item.clone() } }; if frame .input_text( create_item_label(&item_tag, "value_input", &prev_item_tags), item, ) .build() { *data_changed = true; } } ItemType::CowStr => { frame.same_line(); let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::>() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::>() else { unreachable!(); }; &mut item.clone() } }; if frame .input_text( create_item_label(&item_tag, "value_input", &prev_item_tags), item.to_mut(), ) .build() { *data_changed = true; } } ItemType::Reflected(_) => unimplemented!(), } } fn create_scalar_item_input( frame: &dear_imgui_bindings::Ui, item: &mut ItemRef<'_>, data_changed: &mut bool, item_tag: &str, prev_item_tags: &[&str], ) where Scalar: dear_imgui_bindings::internal::DataTypeKind + Clone + 'static, { let item = match item { ItemRef::Mutable(item) => { let Some(item) = (*item).downcast_mut::() else { unreachable!(); }; item } ItemRef::Immutable(item) => { let Some(item) = (*item).downcast_ref::() else { unreachable!(); }; &mut item.clone() } }; if frame .input_scalar( create_item_label(item_tag, "value_input", &prev_item_tags), item, ) .build() { *data_changed = true; } } fn create_enum_widgets( frame: &dear_imgui_bindings::Ui, item: &mut ItemRef<'_>, data_changed: &mut bool, item_tag: &str, item_is_read_only: bool, enum_type: &EnumReflection, prev_item_tags: &[&str], ) { let Some(mut curr_variant_index) = (enum_type.get_variant_index)(item.as_ref()) else { unreachable!(); }; if create_combo_box( frame, create_item_label(&item_tag, "variant_select", &prev_item_tags), &mut curr_variant_index, enum_type.variants, |variant| { let disabled = variant.fields.as_ref().and_then(|fields| { fields.fields().iter().find_map(|field| { let field_name = match field.name { Some(field_name) => Either::A(field_name), None => Either::B(field.index), }; let Some(field_type) = field.type_reflection() else { return Some((field_name, "has no type reflection")); }; if !field_type.has_default_value() { return Some((field_name, "has no default value")); } None }) }); ComboBoxItem { label: variant.name.into(), disabled: disabled.is_some(), hover_tooltip: disabled.map(|(bad_field_name, bad_field_reason)| { format!( "Variant cannot be selected since field {} {}", bad_field_name, bad_field_reason ) .into() }), } }, ) { 'used: { let ItemRef::Mutable(item) = item else { break 'used; }; let new_variant = &enum_type.variants[curr_variant_index]; let Ok(()) = (new_variant.try_write_new_to)( *item, &mut new_variant .fields .iter() .map(|fields| fields.fields()) .flatten() .map(|field| { let Some(field_type) = field.type_reflection() else { // Variants with any field that is missing type reflection is // disabled so that the user cannot select it unreachable!(); }; let Some(default_value) = field_type.default_value() else { unreachable!(); }; default_value }), ) else { unreachable!(); }; *data_changed = true; } } let mut prev_item_tags = prev_item_tags.to_vec(); prev_item_tags.push(item_tag); let curr_variant = &enum_type.variants[curr_variant_index]; if let Some(curr_variant_fields) = &curr_variant.fields { for (field_index, field) in curr_variant_fields.fields().iter().enumerate() { let Some(field_item_type) = get_item_type(field.type_reflection(), field.type_id) else { continue; }; let field_name: Cow = match field.name { Some(field_name) => field_name.into(), None => field_index.to_string().into(), }; let Ok(field_item) = (match item { ItemRef::Mutable(item) => { (curr_variant.try_get_field_mut)(*item, field_index) .map(ItemRef::Mutable) } ItemRef::Immutable(item) => { (curr_variant.try_get_field)(*item, field_index) .map(ItemRef::Immutable) } }) else { unreachable!(); }; add_item_to_frame( frame, field_item, data_changed, ItemInfo { item_title: field_name.as_ref(), item_tag: field_name.as_ref(), item_type_name: Some(field.type_name()), item_type: field_item_type, item_is_read_only: item_is_read_only, }, &prev_item_tags, ); } }; } fn create_item_label(item_tag: &str, value_name: &str, prev_item_tags: &[&str]) -> String { let mut item_label = String::with_capacity( 2 + item_tag.len() + prev_item_tags.len() + prev_item_tags.iter().max().unwrap_or(&"".into()).len(), ); item_label.push_str("##"); for prev_item_tag in prev_item_tags { item_label.push('/'); item_label.push_str(prev_item_tag); } item_label.push('/'); item_label.push_str(item_tag); item_label.push(':'); item_label.push_str(value_name); item_label } fn get_whole_type_is_user_editable( ty: Option<&'static TypeReflection>, type_id: TypeId, ) -> bool { let Some(item_type) = get_item_type(ty, type_id) else { return false; }; match item_type { ItemType::Reflected(TypeReflection::Struct(struct_ty)) => { for field in struct_ty.fields { if matches!( field.visibility, Visibility::Private | Visibility::PubScoped(_) ) { continue; } if !get_whole_type_is_user_editable( field.type_reflection(), field.type_id, ) { return false; } } true } ItemType::Reflected(TypeReflection::Enum(enum_ty)) => { enum_ty.variants.iter().any(|variant| { for field in variant.fields.iter().flat_map(|fields| fields.fields()) { if !get_whole_type_is_user_editable( field.type_reflection(), field.type_id, ) { return false; } } true }) } ItemType::Reflected( TypeReflection::Slice(_) | TypeReflection::Array(_) | TypeReflection::Literal(_), ) | ItemType::String | ItemType::CowStr => true, ItemType::Reflected(_) => unimplemented!(), } } struct ComboBoxItem<'b> { label: Cow<'b, str>, disabled: bool, hover_tooltip: Option>, } fn create_combo_box( frame: &dear_imgui_bindings::Ui, label: impl AsRef, current_item: &mut usize, items: &[Item], item_fn: ItemFn, ) -> bool where for<'b> ItemFn: Fn(&'b Item) -> ComboBoxItem<'b>, { let item_fn = &item_fn; let mut result = false; let preview_label = items.get(*current_item).map(|item| item_fn(item).label); if let Some(combo_token) = frame.begin_combo( label, preview_label .as_ref() .map(|preview_label| preview_label.as_ref()) .unwrap_or(""), ) { for (idx, item) in items.iter().enumerate() { let is_selected = idx == *current_item; if is_selected { frame.set_item_default_focus(); } let ComboBoxItem { label: item_label, disabled: item_disabled, hover_tooltip: item_hover_tooltip, } = item_fn(item); let disabled_token = frame.begin_disabled_with_cond(item_disabled); let clicked = frame.selectable(item_label.as_ref()); if clicked { *current_item = idx; result = true; } disabled_token.end(); if let Some(item_hover_tooltip) = item_hover_tooltip { if frame.is_item_hovered_with_flags( dear_imgui_bindings::ItemHoveredFlags::ALLOW_WHEN_DISABLED, ) { frame.tooltip_text(item_hover_tooltip.as_ref()); } } } combo_token.end(); } result } struct ListBoxItem<'b> { label: Cow<'b, str>, disabled: bool, hover_tooltip: Option>, } trait ListBoxExt { fn build_extended( self, ui: &dear_imgui_bindings::Ui, current_item: &mut i32, items: &[Item], item_fn: &ItemFn, ) -> bool where for<'b> ItemFn: Fn(&'b Item) -> ListBoxItem<'b>; } impl