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:
@@ -7,6 +7,7 @@ pub mod style;
|
|||||||
pub mod types;
|
pub mod types;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
pub use rhei::RheiContext;
|
pub use rhei::RheiContext;
|
||||||
use style::StyleSheet as SS;
|
use style::StyleSheet as SS;
|
||||||
pub use types::{ComponentDef, Document, Element, Interner, InterpError};
|
pub use types::{ComponentDef, Document, Element, Interner, InterpError};
|
||||||
@@ -476,6 +477,7 @@ impl Interpreter {
|
|||||||
vcomp.children = Self::evaluate_vdom_incr(
|
vcomp.children = Self::evaluate_vdom_incr(
|
||||||
&comp_child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
|
&comp_child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
|
||||||
);
|
);
|
||||||
|
vcomp.content_hash = Self::compute_content_hash(&vcomp);
|
||||||
output.push(vcomp);
|
output.push(vcomp);
|
||||||
|
|
||||||
for (k, old) in old_vals.into_iter().rev() {
|
for (k, old) in old_vals.into_iter().rev() {
|
||||||
@@ -509,6 +511,7 @@ impl Interpreter {
|
|||||||
vnode.children = Self::evaluate_vdom_incr(
|
vnode.children = Self::evaluate_vdom_incr(
|
||||||
&child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
|
&child_refs, variables, components, rhei, stylesheet, &child_ancestors, dirty_set,
|
||||||
);
|
);
|
||||||
|
vnode.content_hash = Self::compute_content_hash(&vnode);
|
||||||
output.push(vnode);
|
output.push(vnode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -518,6 +521,20 @@ impl Interpreter {
|
|||||||
output
|
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> {
|
fn build_ancestor_chain<'a>(ancestors: &[AncestorInfo], el: &Element) -> Vec<AncestorInfo> {
|
||||||
let mut chain = ancestors.to_vec();
|
let mut chain = ancestors.to_vec();
|
||||||
let id = el.id().map(String::from);
|
let id = el.id().map(String::from);
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ pub struct Element<'a> {
|
|||||||
pub children: Vec<Element<'a>>,
|
pub children: Vec<Element<'a>>,
|
||||||
pub computed_style: ComputedStyle,
|
pub computed_style: ComputedStyle,
|
||||||
pub element_id: ElementId,
|
pub element_id: ElementId,
|
||||||
|
pub content_hash: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Element<'a> {
|
impl<'a> Element<'a> {
|
||||||
@@ -183,6 +184,7 @@ impl<'a> Element<'a> {
|
|||||||
children: Vec::with_capacity(4),
|
children: Vec::with_capacity(4),
|
||||||
computed_style: ComputedStyle::default(),
|
computed_style: ComputedStyle::default(),
|
||||||
element_id: ElementId(u32::MAX),
|
element_id: ElementId(u32::MAX),
|
||||||
|
content_hash: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,6 +195,7 @@ impl<'a> Element<'a> {
|
|||||||
children: Vec::with_capacity(4),
|
children: Vec::with_capacity(4),
|
||||||
computed_style: ComputedStyle::default(),
|
computed_style: ComputedStyle::default(),
|
||||||
element_id: id,
|
element_id: id,
|
||||||
|
content_hash: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,25 @@ use iced::widget::{
|
|||||||
use iced::{Alignment, Background, Border, Length, Theme};
|
use iced::{Alignment, Background, Border, Length, Theme};
|
||||||
use iced::font::Weight;
|
use iced::font::Weight;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static WIDGET_ID_CACHE: RefCell<HashMap<(u32, &'static str), iced::widget::Id>> = RefCell::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_or_create_widget_id(key: u32, prefix: &'static str) -> iced::widget::Id {
|
||||||
|
WIDGET_ID_CACHE.with(|cache| {
|
||||||
|
cache.borrow_mut()
|
||||||
|
.entry((key, prefix))
|
||||||
|
.or_insert_with(|| {
|
||||||
|
let s = Box::leak(format!("{prefix}:{key}").into_boxed_str());
|
||||||
|
iced::widget::Id::new(s)
|
||||||
|
})
|
||||||
|
.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn extract_var_binding(el: &Element, prop: &str) -> Option<String> {
|
fn extract_var_binding(el: &Element, prop: &str) -> Option<String> {
|
||||||
el.properties.iter()
|
el.properties.iter()
|
||||||
.find_map(|(k, v)| {
|
.find_map(|(k, v)| {
|
||||||
@@ -378,7 +395,8 @@ fn apply_universal_box_model<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sid) = scrollable_id {
|
if let Some(sid) = scrollable_id {
|
||||||
s = s.on_scroll(move |viewport| Message::ScrollableScrolled(sid, viewport.absolute_offset().y));
|
s = s.id(get_or_create_widget_id(sid as u32, "sc"))
|
||||||
|
.on_scroll(move |viewport| Message::ScrollableScrolled(sid, viewport.absolute_offset().y));
|
||||||
}
|
}
|
||||||
|
|
||||||
s.into()
|
s.into()
|
||||||
@@ -1167,6 +1185,7 @@ fn render_input<'a>(
|
|||||||
|
|
||||||
let mut input = text_input(&placeholder, &value)
|
let mut input = text_input(&placeholder, &value)
|
||||||
.on_input(move |v| Message::InputChanged(var_name.clone(), v))
|
.on_input(move |v| Message::InputChanged(var_name.clone(), v))
|
||||||
|
.id(get_or_create_widget_id(el.element_id.0, "ti"))
|
||||||
.padding(padding);
|
.padding(padding);
|
||||||
|
|
||||||
if let Some(w) = cs.width {
|
if let Some(w) = cs.width {
|
||||||
|
|||||||
Reference in New Issue
Block a user