Skip to main content

egui_graph_edit/
node_finder.rs

1use std::{collections::BTreeMap, marker::PhantomData};
2
3use crate::{color_hex_utils::*, CategoryTrait, NodeTemplateIter, NodeTemplateTrait};
4
5use egui::*;
6
7#[derive(Clone)]
8#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
9pub struct NodeFinder<NodeTemplate> {
10    pub query: String,
11    /// Reset every frame. When set, the node finder will be moved at that position
12    pub position: Option<Pos2>,
13    pub just_spawned: bool,
14    _phantom: PhantomData<NodeTemplate>,
15}
16
17impl<NodeTemplate, NodeData, UserState, CategoryType> NodeFinder<NodeTemplate>
18where
19    NodeTemplate:
20        NodeTemplateTrait<NodeData = NodeData, UserState = UserState, CategoryType = CategoryType>,
21    CategoryType: CategoryTrait,
22{
23    pub fn new_at(pos: Pos2) -> Self {
24        NodeFinder {
25            query: "".into(),
26            position: Some(pos),
27            just_spawned: true,
28            _phantom: Default::default(),
29        }
30    }
31
32    /// Shows the node selector panel with a search bar. Returns whether a node
33    /// archetype was selected and, in that case, the finder should be hidden on
34    /// the next frame.
35    pub fn show(
36        &mut self,
37        ui: &mut Ui,
38        all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
39        user_state: &mut UserState,
40    ) -> Option<NodeTemplate> {
41        let background_color;
42        let text_color;
43
44        if ui.visuals().dark_mode {
45            background_color = color_from_hex("#3f3f3f").unwrap();
46            text_color = color_from_hex("#fefefe").unwrap();
47        } else {
48            background_color = color_from_hex("#fefefe").unwrap();
49            text_color = color_from_hex("#3f3f3f").unwrap();
50        }
51
52        ui.visuals_mut().widgets.noninteractive.fg_stroke = Stroke::new(2.0f32, text_color);
53
54        let frame = Frame::dark_canvas(ui.style())
55            .fill(background_color)
56            .inner_margin(vec2(5.0, 5.0));
57
58        // The archetype that will be returned.
59        let mut submitted_archetype = None;
60        frame.show(ui, |ui| {
61            ui.vertical(|ui| {
62                let resp = ui.text_edit_singleline(&mut self.query);
63                if self.just_spawned {
64                    resp.request_focus();
65                    self.just_spawned = false;
66                }
67                let update_open = resp.changed();
68
69                let mut query_submit = resp.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter));
70
71                let max_height = ui.input(|i| i.content_rect().height() * 0.5);
72                let scroll_area_width = resp.rect.width() - 30.0;
73
74                let all_kinds = all_kinds.all_kinds();
75                let mut categories: BTreeMap<String, Vec<&NodeTemplate>> = Default::default();
76                let mut orphan_kinds = Vec::new();
77
78                for kind in &all_kinds {
79                    let kind_categories = kind.node_finder_categories(user_state);
80
81                    if kind_categories.is_empty() {
82                        orphan_kinds.push(kind);
83                    } else {
84                        for category in kind_categories {
85                            categories.entry(category.name()).or_default().push(kind);
86                        }
87                    }
88                }
89
90                Frame::default()
91                    .inner_margin(vec2(10.0, 10.0))
92                    .show(ui, |ui| {
93                        ScrollArea::vertical()
94                            .min_scrolled_height(max_height)
95                            .max_height(max_height)
96                            .show(ui, |ui| {
97                                ui.set_width(scroll_area_width);
98                                for (category, kinds) in categories {
99                                    let filtered_kinds: Vec<_> = kinds
100                                        .into_iter()
101                                        .map(|kind| {
102                                            let kind_name =
103                                                kind.node_finder_label(user_state).to_string();
104                                            (kind, kind_name)
105                                        })
106                                        .filter(|(_kind, kind_name)| {
107                                            kind_name
108                                                .to_lowercase()
109                                                .contains(self.query.to_lowercase().as_str())
110                                        })
111                                        .collect();
112
113                                    if !filtered_kinds.is_empty() {
114                                        let default_open = !self.query.is_empty();
115
116                                        CollapsingHeader::new(&category)
117                                            .default_open(default_open)
118                                            .open(update_open.then_some(default_open))
119                                            .show(ui, |ui| {
120                                                for (kind, kind_name) in filtered_kinds {
121                                                    if ui
122                                                        .selectable_label(false, kind_name)
123                                                        .clicked()
124                                                    {
125                                                        submitted_archetype = Some(kind.clone());
126                                                    } else if query_submit {
127                                                        submitted_archetype = Some(kind.clone());
128                                                        query_submit = false;
129                                                    }
130                                                }
131                                            });
132                                    }
133                                }
134
135                                for kind in orphan_kinds {
136                                    let kind_name = kind.node_finder_label(user_state).to_string();
137
138                                    if ui.selectable_label(false, kind_name).clicked() {
139                                        submitted_archetype = Some(kind.clone());
140                                    } else if query_submit {
141                                        submitted_archetype = Some(kind.clone());
142                                        query_submit = false;
143                                    }
144                                }
145                            });
146                    });
147            });
148        });
149
150        submitted_archetype
151    }
152}