Skip to content

Commit 79f086b

Browse files
committed
chore: improve bone id widget; now it is an autocomplete-style text input
1 parent 940f68c commit 79f086b

4 files changed

Lines changed: 111 additions & 16 deletions

File tree

crates/bevy_animation_graph_editor/src/ui/editor_windows/ragdoll_editor/body_mapping_inspector.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use bevy_animation_graph::core::{ragdoll::bone_mapping::BodyMapping, skeleton::Skeleton};
22
use egui::Widget;
33

4-
use crate::ui::generic_widgets::{bone_id::BoneIdWidget, isometry3d::Isometry3dWidget};
4+
use crate::ui::generic_widgets::{bone_id_via_path::BoneIdWidget, isometry3d::Isometry3dWidget};
55

66
pub struct BodyMappingInspector<'a> {
77
pub body_mapping: &'a mut BodyMapping,
@@ -20,10 +20,8 @@ impl Widget for BodyMappingInspector<'_> {
2020

2121
response |= ui.label("target bone:");
2222
let mut bone_id = bone.bone.id();
23-
response |= ui.add(
24-
BoneIdWidget::new_salted(&mut bone_id, "bone id picker")
25-
.with_skeleton(Some(self.skeleton)),
26-
);
23+
response |=
24+
ui.add(BoneIdWidget::new(&mut bone_id, self.skeleton).salted("bone id picker"));
2725
if let Some(path) = self.skeleton.id_to_path(bone_id) {
2826
bone.bone = path;
2927
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use bevy_animation_graph::core::{animation_clip::EntityPath, id::BoneId, skeleton::Skeleton};
2+
3+
use crate::ui::generic_widgets::entity_path::EntityPathWidget;
4+
5+
pub struct BoneIdWidget<'a> {
6+
pub bone_id: &'a mut BoneId,
7+
pub id_hash: egui::Id,
8+
pub skeleton: &'a Skeleton,
9+
}
10+
11+
impl<'a> BoneIdWidget<'a> {
12+
pub fn new(bone_id: &'a mut BoneId, skeleton: &'a Skeleton) -> Self {
13+
Self {
14+
bone_id,
15+
id_hash: egui::Id::new("bone id widget"),
16+
skeleton,
17+
}
18+
}
19+
20+
pub fn salted(mut self, salt: impl std::hash::Hash) -> Self {
21+
self.id_hash = egui::Id::new(salt);
22+
self
23+
}
24+
}
25+
26+
impl<'a> egui::Widget for BoneIdWidget<'a> {
27+
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
28+
ui.push_id(self.id_hash, |ui| {
29+
let current_path = self.skeleton.id_to_path(*self.bone_id).unwrap_or_default();
30+
let mut buffer = Buffer::from_ui(ui, *self.bone_id, current_path);
31+
32+
let response = ui.add(
33+
EntityPathWidget::new_salted(&mut buffer.path, "bone id entity path")
34+
.with_options(self.skeleton.iter_paths()),
35+
);
36+
buffer.write_back(ui);
37+
38+
if response.changed() {
39+
*self.bone_id = buffer.path.id();
40+
}
41+
42+
response
43+
})
44+
.inner
45+
}
46+
}
47+
48+
#[derive(Clone)]
49+
pub struct Buffer {
50+
original_id: BoneId,
51+
path: EntityPath,
52+
}
53+
54+
impl Buffer {
55+
pub fn from_ui(ui: &mut egui::Ui, bone_id: BoneId, path: EntityPath) -> Self {
56+
let id = Self::id(ui);
57+
ui.memory_mut(|mem| {
58+
let new = move || Self {
59+
original_id: bone_id,
60+
path: path.clone(),
61+
};
62+
let val = mem.data.get_temp_mut_or_insert_with(id, &new).clone();
63+
if val.original_id == bone_id {
64+
val
65+
} else {
66+
new()
67+
}
68+
})
69+
}
70+
pub fn write_back(&self, ui: &mut egui::Ui) {
71+
let id = Self::id(ui);
72+
ui.memory_mut(|mem| mem.data.insert_temp(id, self.clone()));
73+
}
74+
75+
fn id(ui: &egui::Ui) -> egui::Id {
76+
ui.id().with("bone id buffer")
77+
}
78+
}

crates/bevy_animation_graph_editor/src/ui/generic_widgets/entity_path.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,35 +46,53 @@ impl<'a> egui::Widget for EntityPathWidget<'a> {
4646
.clone()
4747
});
4848

49-
let response = ui.add(
49+
let text_edit_response = ui.add(
5050
egui::TextEdit::singleline(&mut buffer.value).min_size(egui::Vec2::new(350., 0.)),
5151
);
5252

53+
let mut final_response = text_edit_response.clone();
54+
5355
let top_k = self
5456
.options
5557
.iter()
5658
.filter(|opt| opt.to_slashed_string().starts_with(&buffer.value))
5759
.take(10)
5860
.collect::<Vec<_>>();
5961

60-
if !top_k.is_empty() && response.has_focus() {
61-
response.show_tooltip_ui(|ui| {
62+
egui::Popup::menu(&text_edit_response)
63+
.close_behavior(egui::PopupCloseBehavior::CloseOnClickOutside)
64+
.open_memory(
65+
(!top_k.is_empty() && text_edit_response.has_focus())
66+
.then_some(egui::SetOpenCommand::Bool(true)),
67+
)
68+
.show(|ui| {
6269
for opt in top_k {
6370
let slashed = opt.to_slashed_string();
6471
let Some(rest) = slashed.strip_prefix(&buffer.value) else {
6572
continue;
6673
};
6774

68-
ui.horizontal(|ui| {
69-
ui.spacing_mut().item_spacing = egui::Vec2::ZERO;
70-
ui.label(egui::RichText::new(&buffer.value).strong());
71-
ui.label(egui::RichText::new(rest));
72-
});
75+
let r = ui
76+
.horizontal(|ui| {
77+
ui.spacing_mut().item_spacing = egui::Vec2::ZERO;
78+
let r = ui.add(
79+
egui::Button::new((
80+
egui::RichText::new(&buffer.value).strong(),
81+
egui::RichText::new(rest),
82+
))
83+
.frame(false),
84+
);
85+
r
86+
})
87+
.inner;
88+
if r.clicked() {
89+
*self.entity_path = (*opt).clone();
90+
final_response.mark_changed();
91+
}
7392
}
7493
});
75-
}
7694

77-
if response.changed()
95+
if text_edit_response.changed()
7896
&& let Some(new_path) =
7997
EntityPath::from_slashed_string_if_safe(buffer.value.clone())
8098
{
@@ -83,7 +101,7 @@ impl<'a> egui::Widget for EntityPathWidget<'a> {
83101

84102
ui.memory_mut(|mem| mem.data.insert_temp(buffer_id, buffer));
85103

86-
response
104+
final_response
87105
})
88106
.inner
89107
}

crates/bevy_animation_graph_editor/src/ui/generic_widgets/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod asset_picker;
44
pub mod body_id;
55
pub mod body_mode;
66
pub mod bone_id;
7+
pub mod bone_id_via_path;
78
pub mod bone_mask;
89
pub mod data_spec_widget;
910
pub mod data_value;

0 commit comments

Comments
 (0)