use crate::layout::damage::ALL_DAMAGE;
use crate::{BaseDocument, NodeId, node::NodeFlags, util::Color};
use style::invalidation::element::restyle_hints::RestyleHint;
use style_dom::ElementState;
pub const MAX_TOP_LAYERS: usize = 1024;
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct TopLayerConfig {
pub modal: bool,
pub contain_scroll: bool,
pub backdrop: Option<Color>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TopLayer {
pub node_id: NodeId,
pub config: TopLayerConfig,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TopLayerError {
MissingNode,
InvalidTarget,
InvalidBackdrop,
Capacity,
}
impl BaseDocument {
pub fn top_layers(&self) -> &[TopLayer] {
&self.top_layers
}
pub fn top_layer_is_rendered(&self, node_id: NodeId) -> bool {
let mut cursor = Some(node_id);
while let Some(id) = cursor {
let Some(node) = self.nodes.get(id) else {
return false;
};
if !node.flags.is_in_document() {
return false;
}
if node.is_element() && node.primary_styles().is_none() {
return false;
}
if node.display_style().is_some_and(|display| {
display.inside() == style::values::specified::box_::DisplayInside::None
}) {
return false;
}
cursor = node.parent;
}
true
}
pub fn set_top_layer(
&mut self,
node_id: NodeId,
config: Option<TopLayerConfig>,
) -> Result<bool, TopLayerError> {
let node = self.nodes.get(node_id).ok_or(TopLayerError::MissingNode)?;
if config
.and_then(|c| c.backdrop)
.is_some_and(|color| color.components.iter().any(|v| !v.is_finite()))
{
return Err(TopLayerError::InvalidBackdrop);
}
let index = self
.top_layers
.iter()
.position(|entry| entry.node_id == node_id);
if config.is_some()
&& (!node.is_element()
|| !node.flags.is_in_document()
|| self
.try_root_element()
.is_some_and(|root| root.id == node_id))
{
return Err(TopLayerError::InvalidTarget);
}
if config.is_some() && index.is_none() && self.top_layers.len() >= MAX_TOP_LAYERS {
return Err(TopLayerError::Capacity);
}
if index.map(|i| self.top_layers[i].config) == config {
return Ok(false);
}
match (index, config) {
(Some(i), Some(config)) => self.top_layers[i].config = config,
(Some(i), None) => {
self.top_layers.remove(i);
}
(None, Some(config)) => self.top_layers.push(TopLayer { node_id, config }),
(None, None) => return Ok(false),
}
self.update_top_layer_state(node_id, config);
self.changed_nodes.insert(node_id);
self.shell_provider.request_redraw();
Ok(true)
}
pub(crate) fn retire_top_layer(&mut self, node_id: NodeId) {
if let Some(index) = self
.top_layers
.iter()
.position(|entry| entry.node_id == node_id)
{
self.top_layers.remove(index);
self.update_top_layer_state(node_id, None);
}
}
fn update_top_layer_state(&mut self, node_id: NodeId, config: Option<TopLayerConfig>) {
let state = ElementState::POPOVER_OPEN | ElementState::MODAL;
self.snapshot_node_and(node_id, state, |node| {
node.flags.set(NodeFlags::IS_TOP_LAYER, config.is_some());
node.flags.set(
NodeFlags::CONTAIN_TOP_LAYER_SCROLL,
config.is_some_and(|c| c.contain_scroll),
);
let element_state = node.element_state_mut();
element_state.set(ElementState::POPOVER_OPEN, config.is_some());
element_state.set(ElementState::MODAL, config.is_some_and(|c| c.modal));
node.insert_damage(ALL_DAMAGE);
node.set_restyle_hint(RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS);
node.mark_ancestors_dirty();
});
if let Some(parent) = self.nodes[node_id].parent {
self.nodes[parent].insert_damage(ALL_DAMAGE);
}
self.nodes[node_id].oof_containing_block.set(None);
self.nodes[node_id].layout_parent.set(None);
}
}