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}