use crate::{
Command, Composer, ComposerCore, DirtyBubble, NodeId, RecomposeScope,
debug_scope_invalidation_sources, debug_scope_label,
};
#[derive(Clone, Copy)]
pub(crate) enum RecomposeChildCursor {
Unknown,
After { first: NodeId, placed: usize },
At(usize),
}
impl RecomposeChildCursor {
fn at_first_root(parent_hint: Option<NodeId>, first_root: Option<NodeId>) -> Self {
match (parent_hint, first_root) {
(Some(_), Some(first)) => Self::After { first, placed: 0 },
_ => Self::Unknown,
}
}
}
impl Composer {
fn begin_scope_group(&self, scope: &RecomposeScope) -> Option<Option<NodeId>> {
let started = self.with_slot_session_mut(|slots| {
slots
.begin_recompose_at_scope(scope)
.map(|group| (group, slots.open_group_first_root_node()))
});
log::trace!(
target: "cranpose::compose::recompose",
"scope_id={} label={:?} started_at={:?} sources={:?}",
scope.id(),
debug_scope_label(scope.id()),
started.map(|(group, _)| group),
debug_scope_invalidation_sources(scope.id()),
);
started.map(|(_, first_root)| first_root)
}
pub(crate) fn resolve_recompose_child_cursor(&self) -> RecomposeChildCursor {
let cursor = self.core.recompose_child_cursor.get();
let RecomposeChildCursor::After { first, placed } = cursor else {
return cursor;
};
let index = self.core.recompose_parent_hint.get().and_then(|parent| {
let mut applier = self.borrow_applier();
applier.get_mut(parent).ok()?.owned_child_index(first)
});
let resolved = index.map_or(RecomposeChildCursor::Unknown, |index| {
RecomposeChildCursor::At(index + placed)
});
self.core.recompose_child_cursor.set(resolved);
resolved
}
pub(crate) fn recompose_group(&self, scope: &RecomposeScope) {
struct RecomposeGuard<'a> {
composer: &'a Composer,
scope: &'a RecomposeScope,
}
impl Drop for RecomposeGuard<'_> {
fn drop(&mut self) {
self.composer.close_current_group_body_for_scope(self.scope);
#[expect(
clippy::redundant_closure_for_method_calls,
reason = "the method path is not general over the session lifetime"
)]
self.composer
.with_slot_session_mut(|slots| slots.end_recompose());
log::trace!(
target: "cranpose::compose::recompose",
"scope_id={} label={:?} ended",
self.scope.id(),
debug_scope_label(self.scope.id()),
);
self.scope.mark_recomposed();
if let Err(err) = self.composer.flush_pending_commands_if_large() {
log::error!("mid-recomposition command flush failed: {err}");
}
}
}
if !scope.is_effectively_active() {
scope.defer_until_reactivated();
return;
}
if !scope.is_invalid() {
scope.mark_recomposed();
return;
}
if let Some(first_root) = self.begin_scope_group(scope) {
let parent_hint = scope.parent_hint();
let previous_cursor = self.resolve_recompose_child_cursor();
let cursor = RecomposeChildCursor::at_first_root(parent_hint, first_root);
let previous_hint = self.core.recompose_parent_hint.replace(parent_hint);
self.core.recompose_child_cursor.set(cursor);
struct HintGuard<'a> {
core: &'a ComposerCore,
previous: Option<NodeId>,
previous_cursor: RecomposeChildCursor,
}
impl Drop for HintGuard<'_> {
fn drop(&mut self) {
self.core.recompose_parent_hint.set(self.previous);
self.core.recompose_child_cursor.set(self.previous_cursor);
}
}
let _hint_guard = HintGuard {
core: &self.core,
previous: previous_hint,
previous_cursor,
};
{
let mut stack = self.scope_stack();
stack.push(scope.clone());
}
let guard = RecomposeGuard {
composer: self,
scope,
};
let saved_locals = std::mem::replace(&mut *self.local_stack(), scope.local_stack());
let callback_ran = scope.run_recompose(self);
log::trace!(
target: "cranpose::compose::recompose",
"scope_id={} label={:?} callback_ran={}",
scope.id(),
debug_scope_label(scope.id()),
callback_ran,
);
if !callback_ran {
if let Some(ancestor_scope) = scope.callback_promotion_target() {
ancestor_scope.invalidate();
scope.request_pending_recompose();
} else {
self.request_root_render();
}
self.skip_current_group();
}
*self.local_stack() = saved_locals;
drop(guard);
} else {
if let Some(ancestor_scope) = scope.callback_promotion_target() {
ancestor_scope.invalidate();
} else {
self.request_root_render();
}
if let Some(parent_hint) = scope.parent_hint().or_else(|| self.root()) {
self.commands_mut().push(Command::BubbleDirty {
node_id: parent_hint,
bubble: DirtyBubble::LAYOUT_AND_MEASURE,
});
}
scope.mark_recomposed();
}
}
}