use crate::compat::{HashMap, String, Vec};
#[cfg(all(feature = "serde", widgets_unstripped))]
use serde::{Deserialize, Serialize};
use crate::core::ObjectId;
pub use crate::widget::WidgetKind;
#[derive(Debug, Clone)]
#[cfg_attr(all(feature = "serde", widgets_unstripped), derive(Serialize, Deserialize))]
pub struct WidgetEntry {
pub id: ObjectId,
pub kind: WidgetKind,
pub parent: Option<ObjectId>,
pub label: String,
}
#[derive(Debug, Clone)]
#[cfg_attr(all(feature = "serde", widgets_unstripped), derive(Serialize, Deserialize))]
pub struct WidgetRegistry {
entries: HashMap<ObjectId, WidgetEntry>,
by_kind: HashMap<WidgetKind, Vec<ObjectId>>,
#[cfg_attr(all(feature = "serde", widgets_unstripped), serde(skip))]
children: HashMap<ObjectId, Vec<ObjectId>>,
}
impl WidgetRegistry {
#[cfg(all(feature = "serde", feature = "serde_json", widgets_unstripped))]
fn rebuild_indices(&mut self) {
let mut by_kind: HashMap<WidgetKind, Vec<ObjectId>> = HashMap::new();
let mut children: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
for (id, entry) in &self.entries {
by_kind.entry(entry.kind).or_default().push(*id);
if let Some(parent) = entry.parent {
children.entry(parent).or_default().push(*id);
}
}
self.by_kind = by_kind;
self.children = children;
}
pub fn new() -> Self {
Self { entries: HashMap::new(), by_kind: HashMap::new(), children: HashMap::new() }
}
pub fn register(&mut self, entry: WidgetEntry) {
let kind = entry.kind;
let id = entry.id;
if let Some(old) = self.entries.get(&id) {
if let Some(list) = self.by_kind.get_mut(&old.kind) {
list.retain(|&x| x != id);
}
if let Some(parent) = old.parent {
if let Some(list) = self.children.get_mut(&parent) {
list.retain(|&x| x != id);
}
}
}
self.by_kind.entry(kind).or_default().push(id);
if let Some(parent) = entry.parent {
self.children.entry(parent).or_default().push(id);
}
self.entries.insert(id, entry);
}
pub fn unregister(&mut self, id: ObjectId) {
if let Some(entry) = self.entries.remove(&id) {
if let Some(list) = self.by_kind.get_mut(&entry.kind) {
list.retain(|&x| x != id);
}
if let Some(parent) = entry.parent {
if let Some(list) = self.children.get_mut(&parent) {
list.retain(|&x| x != id);
}
}
}
}
pub fn get(&self, id: ObjectId) -> Option<&WidgetEntry> {
self.entries.get(&id)
}
pub fn find_by_kind(&self, kind: WidgetKind) -> &[ObjectId] {
self.by_kind.get(&kind).map_or(&[], |v| v.as_slice())
}
pub fn children_of(&self, parent_id: ObjectId) -> Vec<&WidgetEntry> {
self.children
.get(&parent_id)
.map(|ids| ids.iter().filter_map(|id| self.entries.get(id)).collect())
.unwrap_or_default()
}
#[cfg(all(feature = "serde", feature = "serde_json", widgets_unstripped))]
pub fn save(&self, path: &str) -> Result<(), String> {
let json = serde_json::to_string_pretty(self).map_err(|e| {
format!(
"{} widget registry entry/entries could not be serialized to JSON: {e}",
self.len()
)
})?;
std::fs::write(path, &json).map_err(|e| {
format!(
"widget registry ({} bytes of JSON) could not be written to '{path}': {e} \
(check that the directory exists and is writable)",
json.len()
)
})
}
#[cfg(all(feature = "serde", feature = "serde_json", widgets_unstripped))]
pub fn load(&mut self, path: &str) -> Result<(), String> {
let json = std::fs::read_to_string(path).map_err(|e| {
format!(
"widget registry file '{path}' could not be read: {e} (check that the path \
exists and is readable)"
)
})?;
let loaded: WidgetRegistry = serde_json::from_str(&json).map_err(|e| {
format!("widget registry file '{path}' is not valid WidgetRegistry JSON: {e}")
})?;
*self = loaded;
self.rebuild_indices();
Ok(())
}
pub fn all_ids(&self) -> impl Iterator<Item = ObjectId> + '_ {
self.entries.keys().copied()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn clear(&mut self) {
self.entries.clear();
self.by_kind.clear();
self.children.clear();
}
}
crate::impl_default_via_new!(WidgetRegistry);
#[cfg(test)]
mod tests {
use super::*;
fn entry(id: ObjectId, parent: Option<ObjectId>) -> WidgetEntry {
WidgetEntry { id, kind: WidgetKind::Button, parent, label: String::new() }
}
#[test]
fn children_index_tracks_parent_changes_and_removal() {
let mut registry = WidgetRegistry::new();
registry.register(entry(1, None));
registry.register(entry(2, Some(1)));
registry.register(entry(3, Some(1)));
assert_eq!(registry.children_of(1).len(), 2);
registry.register(entry(2, Some(3)));
assert_eq!(registry.children_of(1).len(), 1);
assert_eq!(registry.children_of(3).len(), 1);
registry.unregister(3);
assert!(
registry.children_of(1).is_empty(),
"removing child 3 must also leave parent 1's list"
);
assert_eq!(
registry.children_of(3).len(),
1,
"child 2 still names 3 as its parent; the index mirrors the entries, it does not cascade"
);
}
}