Phase 9: Keyed widgets (9.1 + 9.2)

9.1: Add content_hash field to Element, computed during VDOM evaluation
  from type_name + properties + children content_hashes via DefaultHasher.

9.2: Assign stable iced::widget::Id to text_input and scrollable widgets
  so Iced preserves widget state (cursor position, scroll offset) across
  frames. Ids are cached in a thread_local HashMap to avoid leaks.

9.3 skipped: iced::Element is not Clone and contains Box<dyn Widget>,
  making element-level caching impractical without unsafe lifetime hacks.
This commit is contained in:
Glint Dev
2026-07-24 19:53:02 +03:00
parent 28bd450787
commit b6fa841d11
3 changed files with 40 additions and 1 deletions

View File

@@ -7,6 +7,7 @@ pub mod style;
pub mod types;
use std::borrow::Cow;
use std::hash::{Hash, Hasher};
pub use rhei::RheiContext;
use style::StyleSheet as SS;
pub use types::{ComponentDef, Document, Element, Interner, InterpError};
@@ -476,6 +477,7 @@ impl Interpreter {
vcomp.children = Self::evaluate_vdom_incr(
&comp_child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
);
vcomp.content_hash = Self::compute_content_hash(&vcomp);
output.push(vcomp);
for (k, old) in old_vals.into_iter().rev() {
@@ -509,6 +511,7 @@ impl Interpreter {
vnode.children = Self::evaluate_vdom_incr(
&child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
);
vnode.content_hash = Self::compute_content_hash(&vnode);
output.push(vnode);
}
}
@@ -518,6 +521,20 @@ impl Interpreter {
output
}
fn compute_content_hash(el: &Element) -> u64 {
use std::collections::hash_map::DefaultHasher;
let mut h = DefaultHasher::new();
el.type_name.hash(&mut h);
for (k, v) in &el.properties {
k.hash(&mut h);
v.hash(&mut h);
}
for child in &el.children {
child.content_hash.hash(&mut h);
}
h.finish()
}
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo> {
let mut chain = ancestors.to_vec();
let id = el.id().map(String::from);