use std::{
any::Any,
cell::{Cell, RefCell},
fmt,
hash::{Hash, Hasher},
rc::Rc,
};
use cranpose_core::{Applier, MemoryApplier, NodeId};
use cranpose_foundation::{
Constraints, DelegatableNode, InvalidationKind, LayoutModifierNode, Measurable, ModifierNode,
ModifierNodeContext, ModifierNodeElement, NodeCapabilities, NodeState, Size,
};
use cranpose_ui_layout::LayoutModifierMeasureResult;
use super::Modifier;
use crate::{
render_state::{AppContextId, current_app_context, with_app_context_by_id},
widgets::nodes::layout_node::LayoutNode,
};
pub trait WindowRootDescriptor: Any {
fn layout_size(&self) -> Size;
fn as_any(&self) -> &dyn Any;
}
#[derive(Clone)]
pub struct WindowRootEntry {
pub node: NodeId,
pub descriptor: Rc<dyn WindowRootDescriptor>,
}
impl fmt::Debug for WindowRootEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowRootEntry")
.field("node", &self.node)
.finish()
}
}
#[derive(Default)]
pub struct WindowRootRegistry {
entries: RefCell<Vec<WindowRootEntry>>,
revision: Cell<u64>,
}
impl WindowRootRegistry {
fn register(&self, entry: WindowRootEntry) {
let mut entries = self.entries.borrow_mut();
if let Some(existing) = entries.iter_mut().find(|known| known.node == entry.node) {
*existing = entry;
} else {
entries.push(entry);
}
self.bump();
}
fn unregister(&self, node: NodeId) {
let mut entries = self.entries.borrow_mut();
let before = entries.len();
entries.retain(|entry| entry.node != node);
if entries.len() != before {
self.bump();
}
}
fn bump(&self) {
self.revision.set(self.revision.get().wrapping_add(1));
}
pub fn entries(&self) -> Vec<WindowRootEntry> {
self.entries.borrow().clone()
}
pub fn revision(&self) -> u64 {
self.revision.get()
}
}
pub fn window_roots() -> Vec<WindowRootEntry> {
current_app_context().map_or_else(Vec::new, |context| context.window_roots().entries())
}
pub fn window_roots_revision() -> u64 {
current_app_context().map_or(0, |context| context.window_roots().revision())
}
pub fn is_window_root(applier: &mut MemoryApplier, node: NodeId) -> bool {
applier
.with_node::<LayoutNode, _>(node, |layout_node| layout_node.is_window_root())
.unwrap_or(false)
}
pub fn nearest_window_root(applier: &mut MemoryApplier, node: NodeId) -> Option<NodeId> {
let mut current = node;
for _ in 0..100_000 {
let is_root = applier
.with_node::<LayoutNode, _>(current, |layout_node| layout_node.is_window_root())
.unwrap_or(false);
if is_root {
return Some(current);
}
current = applier.get_mut(current).ok()?.parent()?;
}
None
}
pub struct WindowRootNode {
descriptor: Rc<dyn WindowRootDescriptor>,
node_id: Cell<Option<NodeId>>,
owner: Cell<Option<AppContextId>>,
state: NodeState,
}
impl fmt::Debug for WindowRootNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowRootNode")
.field("node_id", &self.node_id.get())
.finish()
}
}
impl WindowRootNode {
pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
Self {
descriptor,
node_id: Cell::new(None),
owner: Cell::new(None),
state: NodeState::new(),
}
}
pub fn set_descriptor(&mut self, descriptor: Rc<dyn WindowRootDescriptor>) {
self.descriptor = descriptor;
self.register();
}
fn entry(&self, node: NodeId) -> WindowRootEntry {
WindowRootEntry {
node,
descriptor: Rc::clone(&self.descriptor),
}
}
fn register(&self) {
let Some(node) = self.node_id.get() else {
return;
};
let Some(context) = current_app_context() else {
log::debug!("window root {node} attached outside an app context");
return;
};
self.owner.set(Some(context.id()));
context.window_roots().register(self.entry(node));
}
fn unregister(&self) {
let (Some(node), Some(owner)) = (self.node_id.get(), self.owner.take()) else {
return;
};
with_app_context_by_id(owner, |context| context.window_roots().unregister(node));
}
}
impl DelegatableNode for WindowRootNode {
fn node_state(&self) -> &NodeState {
&self.state
}
}
impl ModifierNode for WindowRootNode {
fn on_attach(&mut self, context: &mut dyn ModifierNodeContext) {
self.node_id.set(context.node_id());
self.register();
context.invalidate(InvalidationKind::Layout);
}
fn on_detach(&mut self) {
self.unregister();
}
fn as_layout_node(&self) -> Option<&dyn LayoutModifierNode> {
Some(self)
}
fn as_layout_node_mut(&mut self) -> Option<&mut dyn LayoutModifierNode> {
Some(self)
}
}
impl LayoutModifierNode for WindowRootNode {
fn measure(
&self,
_context: &mut dyn ModifierNodeContext,
measurable: &dyn Measurable,
_constraints: Constraints,
) -> LayoutModifierMeasureResult {
let size = self.descriptor.layout_size();
let size = Size::new(size.width.max(0.0), size.height.max(0.0));
let _content = measurable.measure(Constraints {
min_width: 0.0,
max_width: size.width,
min_height: 0.0,
max_height: size.height,
});
LayoutModifierMeasureResult::with_size(size)
}
fn min_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
0.0
}
fn max_intrinsic_width(&self, _measurable: &dyn Measurable, _height: f32) -> f32 {
0.0
}
fn min_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
0.0
}
fn max_intrinsic_height(&self, _measurable: &dyn Measurable, _width: f32) -> f32 {
0.0
}
}
#[derive(Clone)]
pub struct WindowRootElement {
descriptor: Rc<dyn WindowRootDescriptor>,
}
impl WindowRootElement {
pub fn new(descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
Self { descriptor }
}
fn descriptor_address(&self) -> usize {
Rc::as_ptr(&self.descriptor).cast::<()>() as usize
}
}
impl fmt::Debug for WindowRootElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WindowRootElement").finish()
}
}
impl PartialEq for WindowRootElement {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.descriptor, &other.descriptor)
}
}
impl Hash for WindowRootElement {
fn hash<H: Hasher>(&self, state: &mut H) {
self.descriptor_address().hash(state);
}
}
impl ModifierNodeElement for WindowRootElement {
type Node = WindowRootNode;
fn create(&self) -> Self::Node {
WindowRootNode::new(Rc::clone(&self.descriptor))
}
fn update(&self, node: &mut Self::Node) {
node.set_descriptor(Rc::clone(&self.descriptor));
}
fn capabilities(&self) -> NodeCapabilities {
NodeCapabilities::LAYOUT | NodeCapabilities::WINDOW_ROOT
}
fn inspector_name(&self) -> &'static str {
"windowRoot"
}
}
impl Modifier {
pub fn window_root(self, descriptor: Rc<dyn WindowRootDescriptor>) -> Self {
self.then(Self::with_element(WindowRootElement::new(descriptor)))
}
}