Skip to content

Commit 8f0bec2

Browse files
refactor: some deduplication and cleanup of PlistEntry
1 parent c536ced commit 8f0bec2

File tree

2 files changed

+145
-124
lines changed

2 files changed

+145
-124
lines changed

src/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,12 @@ impl eframe::App for PlistOxide {
356356
});
357357
})
358358
.body(|mut body| {
359-
let changed = crate::widgets::entry::PlistEntry::new(
359+
let state = crate::widgets::entry::PlistEntry::new(
360360
Arc::clone(&self.state.root),
361361
vec![],
362362
)
363363
.show(&mut body);
364-
self.state.unsaved |= changed.is_some();
364+
self.state.unsaved |= state != crate::widgets::entry::ChangeState::Unchanged;
365365
self.update_title(ctx);
366366
});
367367
});

src/widgets/entry.rs

Lines changed: 143 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -14,84 +14,6 @@ use serde::{Deserialize, Serialize};
1414
use super::{click_text_edit::ClickableTextEdit, value::PlistValue};
1515
use crate::utils::{ValueType, child_keys, pv_mut};
1616

17-
#[must_use]
18-
fn get_new_key(keys: plist::dictionary::Keys, k: &str) -> String {
19-
keys.filter(|v| (v.as_str() == k) || (v.starts_with(k) && v.ends_with("Duplicate")))
20-
.last()
21-
.map_or_else(|| "New Child".into(), |v| format!("{v} Duplicate"))
22-
}
23-
24-
#[must_use]
25-
pub fn render_menu(resp: &Response, path: &[String], p: &mut Value) -> Option<bool> {
26-
let mut ret = None;
27-
28-
resp.context_menu(|ui| {
29-
match ValueType::from_val(path, p) {
30-
ValueType::Dictionary => {
31-
if ui.button("Add child").clicked() {
32-
let dict = pv_mut(path, p).as_dictionary_mut().unwrap();
33-
dict.insert(
34-
get_new_key(dict.keys(), "New Child"),
35-
Value::String(String::new()),
36-
);
37-
ui.close();
38-
ret = Some(false);
39-
}
40-
if ui.button("Sort").clicked() {
41-
pv_mut(path, p).as_dictionary_mut().unwrap().sort_keys();
42-
ui.close();
43-
ret = Some(false);
44-
}
45-
}
46-
ValueType::Array => {
47-
if ui.button("Add child").clicked() {
48-
pv_mut(path, p)
49-
.as_array_mut()
50-
.unwrap()
51-
.push(Value::String(String::new()));
52-
ui.close();
53-
ret = Some(false);
54-
}
55-
}
56-
_ => {}
57-
}
58-
59-
let Some(k) = path.last() else {
60-
return;
61-
};
62-
63-
if ui.button("Duplicate").clicked() {
64-
match pv_mut(&path[..path.len() - 1], p) {
65-
Value::Dictionary(v) => {
66-
v.insert(get_new_key(v.keys(), k), v.get(k).unwrap().clone());
67-
}
68-
Value::Array(v) => {
69-
v.push(v.get(k.parse::<usize>().unwrap()).unwrap().clone());
70-
}
71-
_ => unreachable!(),
72-
}
73-
ui.close();
74-
ret = Some(false);
75-
}
76-
77-
if ui.button("Remove").clicked() {
78-
match pv_mut(&path[..path.len() - 1], p) {
79-
Value::Dictionary(v) => {
80-
v.remove(k);
81-
}
82-
Value::Array(v) => {
83-
v.remove(k.parse::<usize>().unwrap());
84-
}
85-
_ => unreachable!(),
86-
}
87-
ui.close();
88-
ret = Some(true);
89-
}
90-
});
91-
92-
ret
93-
}
94-
9517
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
9618
struct State {
9719
expanded: bool,
@@ -121,13 +43,126 @@ pub struct PlistEntry {
12143
id: Id,
12244
}
12345

46+
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
47+
pub enum ChangeState {
48+
Unchanged,
49+
Changed,
50+
Removed,
51+
}
52+
53+
impl std::ops::BitOr for ChangeState {
54+
type Output = Self;
55+
56+
fn bitor(self, rhs: Self) -> Self::Output {
57+
self.max(rhs)
58+
}
59+
}
60+
61+
impl std::ops::BitOrAssign for ChangeState {
62+
fn bitor_assign(&mut self, rhs: Self) {
63+
*self = *self | rhs
64+
}
65+
}
66+
12467
impl PlistEntry {
12568
pub fn new(data: Arc<Mutex<Value>>, path: Vec<String>) -> Self {
12669
let id = Id::new(&path);
12770
Self { data, path, id }
12871
}
12972

130-
pub fn show(self, body: &mut TableBody) -> Option<bool> {
73+
#[must_use]
74+
fn get_new_key(keys: plist::dictionary::Keys, k: &str) -> String {
75+
keys.filter(|v| (v.as_str() == k) || (v.starts_with(k) && v.ends_with("Duplicate")))
76+
.last()
77+
.map_or_else(|| "New Child".into(), |v| format!("{v} Duplicate"))
78+
}
79+
80+
#[must_use]
81+
fn render_menu(resp: &Response, path: &[String], p: &mut Value) -> ChangeState {
82+
let mut ret = ChangeState::Unchanged;
83+
84+
egui::Popup::context_menu(resp).show(|ui| {
85+
match ValueType::from_val(path, p) {
86+
ValueType::Dictionary => {
87+
if ui.button("Add child").clicked() {
88+
let dict = pv_mut(path, p).as_dictionary_mut().unwrap();
89+
dict.insert(
90+
Self::get_new_key(dict.keys(), "New Child"),
91+
Value::String(String::new()),
92+
);
93+
ui.close();
94+
ret |= ChangeState::Changed;
95+
}
96+
if ui.button("Sort").clicked() {
97+
pv_mut(path, p).as_dictionary_mut().unwrap().sort_keys();
98+
ui.close();
99+
ret |= ChangeState::Changed;
100+
}
101+
}
102+
ValueType::Array => {
103+
if ui.button("Add child").clicked() {
104+
pv_mut(path, p)
105+
.as_array_mut()
106+
.unwrap()
107+
.push(Value::String(String::new()));
108+
ui.close();
109+
ret |= ChangeState::Changed;
110+
}
111+
}
112+
_ => {}
113+
}
114+
115+
let Some(k) = path.last() else {
116+
return;
117+
};
118+
119+
if ui.button("Duplicate").clicked() {
120+
match pv_mut(&path[..path.len() - 1], p) {
121+
Value::Dictionary(v) => {
122+
v.insert(Self::get_new_key(v.keys(), k), v.get(k).unwrap().clone());
123+
}
124+
Value::Array(v) => {
125+
v.push(v.get(k.parse::<usize>().unwrap()).unwrap().clone());
126+
}
127+
_ => unreachable!(),
128+
}
129+
ui.close();
130+
ret |= ChangeState::Changed;
131+
}
132+
133+
if ui.button("Remove").clicked() {
134+
match pv_mut(&path[..path.len() - 1], p) {
135+
Value::Dictionary(v) => {
136+
v.remove(k);
137+
}
138+
Value::Array(v) => {
139+
v.remove(k.parse::<usize>().unwrap());
140+
}
141+
_ => unreachable!(),
142+
}
143+
ui.close();
144+
ret |= ChangeState::Removed;
145+
}
146+
});
147+
148+
ret
149+
}
150+
151+
fn show_immutable_key(
152+
ui: &mut egui::Ui,
153+
mut s: &str,
154+
path: &[String],
155+
p: &mut Value,
156+
) -> ChangeState {
157+
let resp = ui.add(
158+
TextEdit::singleline(&mut s)
159+
.desired_width(f32::INFINITY)
160+
.frame(false),
161+
);
162+
Self::render_menu(&resp, path, p)
163+
}
164+
165+
pub fn show(self, body: &mut TableBody) -> ChangeState {
131166
let Self { data, mut path, id } = self;
132167
let mut state = State::load(body.ui_mut().ctx(), id).unwrap_or_default();
133168
let mut ty = ValueType::from_val(&path, &data.lock().unwrap());
@@ -136,7 +171,7 @@ impl PlistEntry {
136171
} else {
137172
vec![]
138173
};
139-
let mut ret = None;
174+
let mut ret = ChangeState::Unchanged;
140175
body.row(20.0, |mut row| {
141176
let resp = row
142177
.col(|ui| {
@@ -164,60 +199,43 @@ impl PlistEntry {
164199
&small_icon_response,
165200
);
166201
}
202+
let mut data = data.lock().unwrap();
167203
if path.is_empty() {
168-
let resp = ui.add(
169-
TextEdit::singleline(&mut "Root")
170-
.desired_width(f32::INFINITY)
171-
.frame(false),
172-
);
173-
let v = render_menu(&resp, &path, &mut data.lock().unwrap());
174-
ret = ret.map_or(v, |vv| Some(v.unwrap_or_default() || vv));
204+
ret |= Self::show_immutable_key(ui, "Root", &path, &mut data);
175205
return;
176206
}
177207
let name = path.last().unwrap().clone();
178-
let k = &name;
179-
let mut data = data.lock().unwrap();
180208
let Some(dict) = pv_mut(&path[..path.len() - 1], &mut data).as_dictionary_mut()
181209
else {
182-
let mut s = k.as_str();
183-
let resp = ui.add(
184-
TextEdit::singleline(&mut s)
185-
.desired_width(f32::INFINITY)
186-
.frame(false),
187-
);
188-
let v = render_menu(&resp, &path, &mut data);
189-
ret = ret.map_or(v, |vv| Some(v.unwrap_or_default() || vv));
210+
ret |= Self::show_immutable_key(ui, name.as_str(), &path, &mut data);
190211
return;
191212
};
192213
let dict_clone = dict.clone();
193214
let resp = ui.add(ClickableTextEdit::from_get_set(
194215
|v| {
195216
v.map_or_else(
196-
|| k.clone(),
217+
|| name.clone(),
197218
|val| {
198219
if !dict.contains_key(&val) {
199-
dict.insert(val.clone(), dict.get(k).unwrap().clone());
220+
dict.insert(val.clone(), dict.get(&name).unwrap().clone());
200221
path.last_mut().unwrap().clone_from(&val);
201-
dict.remove(k);
222+
dict.remove(&name);
202223
}
203224
val
204225
},
205226
)
206227
},
207-
move |v| k == v || !dict_clone.contains_key(v),
228+
|v| name == v || !dict_clone.contains_key(v),
208229
false,
209230
));
210231
ui.spacing_mut().item_spacing = prev_item_spacing;
211-
let v = render_menu(&resp, &path, &mut data);
212-
drop(data);
213-
ret = ret.map_or(v, |vv| Some(v.unwrap_or_default() || vv));
232+
ret |= Self::render_menu(&resp, &path, &mut data);
214233
})
215234
.1;
216-
if ret == Some(true) {
235+
if ret == ChangeState::Removed {
217236
return;
218237
}
219-
let v = render_menu(&resp, &path, &mut data.lock().unwrap());
220-
ret = ret.map_or(v, |vv| Some(v.unwrap_or_default() || vv));
238+
ret |= Self::render_menu(&resp, &path, &mut data.lock().unwrap());
221239
row.col(|ui| {
222240
let prev_type = ty;
223241
ComboBox::from_id_salt(id.with("type"))
@@ -236,25 +254,29 @@ impl PlistEntry {
236254
if prev_type != ty {
237255
let mut data = data.lock().unwrap();
238256
*pv_mut(&path, &mut data) = match ty {
239-
ValueType::Array => Value::Array(vec![]),
240-
ValueType::Dictionary => Value::Dictionary(plist::Dictionary::new()),
241-
ValueType::Boolean => Value::Boolean(false),
242-
ValueType::Data => Value::Data(vec![]),
243-
ValueType::Date => Value::Date(plist::Date::from(SystemTime::now())),
244-
ValueType::Real => Value::Real(0.0),
245-
ValueType::Integer => Value::Integer(plist::Integer::from(0)),
246-
ValueType::String => Value::String(String::new()),
257+
ValueType::Array => Value::Array(Default::default()),
258+
ValueType::Dictionary => Value::Dictionary(Default::default()),
259+
ValueType::Boolean => Value::Boolean(Default::default()),
260+
ValueType::Data => Value::Data(Default::default()),
261+
ValueType::Date => Value::Date(SystemTime::now().into()),
262+
ValueType::Real => Value::Real(Default::default()),
263+
ValueType::Integer => Value::Integer(0.into()),
264+
ValueType::String => Value::String(Default::default()),
265+
};
266+
ret |= if prev_type.is_expandable() || ty.is_expandable() {
267+
ChangeState::Removed
268+
} else {
269+
ChangeState::Changed
247270
};
248-
ret = Some(prev_type.is_expandable() || ty.is_expandable());
249271
}
250272
});
251-
if ret == Some(true) {
273+
if ret == ChangeState::Removed {
252274
return;
253275
}
254276
row.col(|ui| {
255277
if !ty.is_expandable() {
256278
if PlistValue::new(&path, Arc::clone(&data)).show(ui) {
257-
ret = ret.or(Some(false));
279+
ret |= ChangeState::Changed;
258280
}
259281

260282
return;
@@ -272,18 +294,17 @@ impl PlistEntry {
272294
}
273295
});
274296
});
275-
if ret == Some(true) {
297+
if ret == ChangeState::Removed {
276298
return ret;
277299
}
278300
if state.expanded {
279301
for k in keys {
280-
let v = Self::new(
302+
ret |= Self::new(
281303
Arc::clone(&data),
282304
path.iter().chain(std::iter::once(&k)).cloned().collect(),
283305
)
284306
.show(body);
285-
ret = ret.map_or(v, |vv| Some(v.unwrap_or_default() || vv));
286-
if v == Some(true) {
307+
if ret == ChangeState::Removed {
287308
break;
288309
}
289310
}

0 commit comments

Comments
 (0)