1use crate::layout::damage::ALL_DAMAGE;
4use crate::{BaseDocument, NodeId, node::NodeFlags, util::Color};
5use style::invalidation::element::restyle_hints::RestyleHint;
6use style_dom::ElementState;
7
8pub const MAX_TOP_LAYERS: usize = 1024;
9
10#[derive(Clone, Copy, Debug, Default, PartialEq)]
11pub struct TopLayerConfig {
12 pub modal: bool,
14 pub contain_scroll: bool,
17 pub backdrop: Option<Color>,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct TopLayer {
23 pub node_id: NodeId,
24 pub config: TopLayerConfig,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum TopLayerError {
29 MissingNode,
30 InvalidTarget,
31 InvalidBackdrop,
32 Capacity,
33}
34
35impl BaseDocument {
36 pub fn top_layers(&self) -> &[TopLayer] {
37 &self.top_layers
38 }
39
40 pub fn top_layer_is_rendered(&self, node_id: NodeId) -> bool {
43 let mut cursor = Some(node_id);
44 while let Some(id) = cursor {
45 let Some(node) = self.nodes.get(id) else {
46 return false;
47 };
48 if !node.flags.is_in_document() {
49 return false;
50 }
51 if node.is_element() && node.primary_styles().is_none() {
52 return false;
53 }
54 if node.display_style().is_some_and(|display| {
55 display.inside() == style::values::specified::box_::DisplayInside::None
56 }) {
57 return false;
58 }
59 cursor = node.parent;
60 }
61 true
62 }
63
64 pub fn set_top_layer(
68 &mut self,
69 node_id: NodeId,
70 config: Option<TopLayerConfig>,
71 ) -> Result<bool, TopLayerError> {
72 let node = self.nodes.get(node_id).ok_or(TopLayerError::MissingNode)?;
73 if config
74 .and_then(|c| c.backdrop)
75 .is_some_and(|color| color.components.iter().any(|v| !v.is_finite()))
76 {
77 return Err(TopLayerError::InvalidBackdrop);
78 }
79 let index = self
80 .top_layers
81 .iter()
82 .position(|entry| entry.node_id == node_id);
83 if config.is_some()
84 && (!node.is_element()
85 || !node.flags.is_in_document()
86 || self
87 .try_root_element()
88 .is_some_and(|root| root.id == node_id))
89 {
90 return Err(TopLayerError::InvalidTarget);
91 }
92 if config.is_some() && index.is_none() && self.top_layers.len() >= MAX_TOP_LAYERS {
93 return Err(TopLayerError::Capacity);
94 }
95 if index.map(|i| self.top_layers[i].config) == config {
96 return Ok(false);
97 }
98 match (index, config) {
99 (Some(i), Some(config)) => self.top_layers[i].config = config,
100 (Some(i), None) => {
101 self.top_layers.remove(i);
102 }
103 (None, Some(config)) => self.top_layers.push(TopLayer { node_id, config }),
104 (None, None) => return Ok(false),
105 }
106 self.update_top_layer_state(node_id, config);
107 self.changed_nodes.insert(node_id);
108 self.shell_provider.request_redraw();
109 Ok(true)
110 }
111
112 pub(crate) fn retire_top_layer(&mut self, node_id: NodeId) {
113 if let Some(index) = self
114 .top_layers
115 .iter()
116 .position(|entry| entry.node_id == node_id)
117 {
118 self.top_layers.remove(index);
119 self.update_top_layer_state(node_id, None);
120 }
121 }
122
123 fn update_top_layer_state(&mut self, node_id: NodeId, config: Option<TopLayerConfig>) {
124 let state = ElementState::POPOVER_OPEN | ElementState::MODAL;
125 self.snapshot_node_and(node_id, state, |node| {
126 node.flags.set(NodeFlags::IS_TOP_LAYER, config.is_some());
127 node.flags.set(
128 NodeFlags::CONTAIN_TOP_LAYER_SCROLL,
129 config.is_some_and(|c| c.contain_scroll),
130 );
131 let element_state = node.element_state_mut();
132 element_state.set(ElementState::POPOVER_OPEN, config.is_some());
133 element_state.set(ElementState::MODAL, config.is_some_and(|c| c.modal));
134 node.insert_damage(ALL_DAMAGE);
135 node.set_restyle_hint(RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_DESCENDANTS);
136 node.mark_ancestors_dirty();
137 });
138 if let Some(parent) = self.nodes[node_id].parent {
141 self.nodes[parent].insert_damage(ALL_DAMAGE);
142 }
143 self.nodes[node_id].oof_containing_block.set(None);
144 self.nodes[node_id].layout_parent.set(None);
145 }
146}