egui_probe/
map.rs

1use std::{
2    collections::{HashMap, hash_map::Entry},
3    fmt::Display,
4    str::FromStr,
5};
6
7use crate::{
8    EguiProbe, Style,
9    collections::{DeleteMe, EguiProbeFrozen},
10    option::option_probe_with,
11};
12
13#[derive(Clone)]
14pub struct HashMapProbeState {
15    pub new_key: String,
16    error: bool,
17}
18
19pub struct HashMapProbe {
20    pub state: HashMapProbeState,
21    dirty: bool,
22    id: egui::Id,
23}
24
25impl HashMapProbe {
26    pub fn load(cx: &egui::Context, id: egui::Id) -> HashMapProbe {
27        let state = cx.data_mut(|d| {
28            d.get_temp_mut_or(
29                id,
30                HashMapProbeState {
31                    new_key: String::new(),
32                    error: false,
33                },
34            )
35            .clone()
36        });
37
38        HashMapProbe {
39            state,
40            dirty: false,
41            id,
42        }
43    }
44
45    pub fn store(self, cx: &egui::Context) {
46        if self.dirty {
47            cx.data_mut(|d| d.insert_temp(self.id, self.state));
48            cx.request_repaint();
49        }
50    }
51
52    pub fn new_key_edit(&mut self, ui: &mut egui::Ui, reduce_text_width: f32) {
53        let text_edit = egui::TextEdit::singleline(&mut self.state.new_key)
54            .hint_text("new key")
55            .text_color_opt(if self.state.error {
56                Some(ui.visuals().error_fg_color)
57            } else {
58                None
59            })
60            .desired_width(ui.spacing().text_edit_width - reduce_text_width);
61
62        let r = ui.add(text_edit);
63        if r.changed() {
64            self.dirty = true;
65            self.state.error = false;
66        }
67    }
68
69    pub const fn key_error(&mut self) {
70        if self.state.error {
71            return;
72        }
73        self.state.error = true;
74        self.dirty = true;
75    }
76
77    pub fn key_accepted(&mut self) {
78        if self.state.new_key.is_empty() {
79            return;
80        }
81        self.state.new_key.clear();
82        self.dirty = true;
83    }
84}
85
86impl<K, V, S> EguiProbe for HashMap<K, V, S>
87where
88    K: Display + FromStr + Eq + std::hash::Hash,
89    V: EguiProbe + Default,
90    S: std::hash::BuildHasher,
91{
92    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
93        let mut changed = false;
94
95        let mut r = ui
96            .horizontal(|ui| {
97                let mut probe = HashMapProbe::load(ui.ctx(), ui.make_persistent_id("HashMapProbe"));
98
99                let mut reduce_text_width = 0.0;
100
101                let r = ui.weak(format!("[{}]", self.len()));
102                reduce_text_width += r.rect.width() + ui.spacing().item_spacing.x;
103
104                let r = ui.small_button(style.add_button_text());
105                if r.clicked() {
106                    if let Ok(key) = K::from_str(&probe.state.new_key) {
107                        match self.entry(key) {
108                            Entry::Occupied(_) => {
109                                probe.key_error();
110                            }
111                            Entry::Vacant(entry) => {
112                                entry.insert(V::default());
113                                probe.key_accepted();
114                            }
115                        }
116                    } else {
117                        probe.key_error();
118                    }
119                    changed = true;
120                }
121
122                reduce_text_width += r.rect.width() + ui.spacing().item_spacing.x;
123
124                probe.new_key_edit(ui, reduce_text_width);
125                probe.store(ui.ctx());
126            })
127            .response;
128
129        if changed {
130            r.mark_changed();
131        }
132
133        r
134    }
135
136    fn iterate_inner(
137        &mut self,
138        ui: &mut egui::Ui,
139        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
140    ) {
141        self.retain(|key, value| {
142            let mut item = DeleteMe {
143                value,
144                delete: false,
145            };
146            f(&key.to_string(), ui, &mut item);
147            !item.delete
148        });
149    }
150}
151
152impl<K, V, S> EguiProbe for EguiProbeFrozen<'_, HashMap<K, V, S>>
153where
154    K: Display + Eq + std::hash::Hash,
155    V: EguiProbe,
156    S: std::hash::BuildHasher,
157{
158    fn probe(&mut self, ui: &mut egui::Ui, _style: &Style) -> egui::Response {
159        ui.weak(format!("[{}]", self.value.len()))
160    }
161
162    fn iterate_inner(
163        &mut self,
164        ui: &mut egui::Ui,
165        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
166    ) {
167        for (key, value) in self.value.iter_mut() {
168            f(&key.to_string(), ui, value);
169        }
170    }
171}
172
173impl<K, V, S> EguiProbe for EguiProbeFrozen<'_, Option<HashMap<K, V, S>>>
174where
175    K: Display + Eq + std::hash::Hash,
176    V: EguiProbe,
177    S: std::hash::BuildHasher + Default,
178{
179    fn probe(&mut self, ui: &mut egui::Ui, style: &Style) -> egui::Response {
180        option_probe_with(
181            self.value,
182            ui,
183            style,
184            || HashMap::with_hasher(S::default()),
185            |value, ui, _style| ui.weak(format!("[{}]", value.len())),
186        )
187    }
188
189    fn iterate_inner(
190        &mut self,
191        ui: &mut egui::Ui,
192        f: &mut dyn FnMut(&str, &mut egui::Ui, &mut dyn EguiProbe),
193    ) {
194        if let Some(map) = self.value {
195            for (key, value) in map.iter_mut() {
196                f(&key.to_string(), ui, value);
197            }
198        }
199    }
200}