zng_wgt_rule_line/
collapse.rs1use std::sync::Arc;
2
3use zng_app::{
4 update::LayoutUpdates,
5 widget::info::{TreeFilter, iter::TreeIterator},
6};
7use zng_wgt::prelude::*;
8
9#[property(LAYOUT - 100)]
13pub fn collapse_scope(child: impl IntoUiNode, mode: impl IntoVar<CollapseMode>) -> UiNode {
14 let mode = mode.into_var();
15 let mut scope: Option<Arc<CollapseScope>> = None;
16 match_node(child, move |c, op| match op {
17 UiNodeOp::Init => {
18 WIDGET.sub_var_layout(&mode);
19 scope = Some(Arc::new(CollapseScope::new(WIDGET.id())));
20 }
21 UiNodeOp::Deinit => {
22 scope = None;
23 }
24 UiNodeOp::Measure { wm, desired_size } => {
25 *desired_size = SCOPE.with_context(&mut scope, || c.measure(wm));
26 }
27 UiNodeOp::Layout { wl, final_size } => {
28 *final_size = SCOPE.with_context(&mut scope, || c.layout(wl));
29
30 let mode = mode.get();
32
33 let maybe_exclusive = Arc::get_mut(scope.as_mut().unwrap());
35 let is_new = maybe_exclusive.is_none();
37 let mut new = CollapseScope::new(WIDGET.id());
38 let s = maybe_exclusive.unwrap_or(&mut new);
39
40 let mut changes = UpdateDeliveryList::new_any();
42 if mode.is_empty() {
43 if !s.collapse.is_empty() {
44 let info = WIDGET.info();
45 let info = info.tree();
46 for id in s.collapse.drain() {
47 if let Some(wgt) = info.get(id) {
48 changes.insert_wgt(&wgt);
49 }
50 }
51 }
52 } else {
53 let info = WIDGET.info();
58 macro_rules! filter {
59 ($iter:expr) => {
60 $iter.tree_filter(|w| {
61 if w.meta().flagged(*COLLAPSE_SKIP_ID) {
62 TreeFilter::SkipAll
63 } else {
64 TreeFilter::Include
65 }
66 })
67 };
68 }
69
70 let mut trim_start = mode.contains(CollapseMode::TRIM_START);
71 let mut trim_end_id = None;
72 if mode.contains(CollapseMode::TRIM_END) {
73 for wgt in filter!(info.descendants().tree_rev()) {
75 if wgt.meta().flagged(*COLLAPSABLE_LINE_ID) {
76 trim_end_id = Some(wgt.id());
77 } else if wgt.descendants_len() == 0 && !wgt.bounds_info().inner_size().is_empty() {
78 break;
80 }
81 }
82 }
83 let mut trim_end = false;
84 let mut merge = false;
85 for wgt in filter!(info.descendants()) {
86 if wgt.meta().flagged(*COLLAPSABLE_LINE_ID) {
87 if let Some(id) = trim_end_id
88 && id == wgt.id()
89 {
90 trim_end_id = None;
91 trim_end = true;
92 }
93 let changed = if trim_start || merge || trim_end {
94 s.collapse.insert(wgt.id())
95 } else {
96 merge = mode.contains(CollapseMode::MERGE);
97 s.collapse.remove(&wgt.id())
98 };
99 if changed && !is_new {
100 changes.insert_wgt(&wgt);
101 }
102 } else if wgt.descendants_len() == 0 && !wgt.bounds_info().inner_size().is_empty() {
103 trim_start = false;
105 merge = false;
106 }
107 }
108 }
109 if is_new {
110 let s = scope.as_mut().unwrap();
111 let info = WIDGET.info();
113 let info = info.tree();
114 for id in s.collapse.symmetric_difference(&new.collapse) {
115 if let Some(wgt) = info.get(*id) {
116 changes.insert_wgt(&wgt);
117 }
118 }
119 if !changes.widgets().is_empty() {
120 scope = Some(Arc::new(new));
121 }
122 }
123
124 if !changes.widgets().is_empty() {
125 *final_size = wl.with_layout_updates(Arc::new(LayoutUpdates::new(changes)), |wl| {
126 SCOPE.with_context(&mut scope, || c.layout(wl))
127 });
128 }
129 }
130 _ => {}
131 })
132}
133
134#[property(CONTEXT, default(false))]
140pub fn collapse_skip(child: impl IntoUiNode, skip: impl IntoVar<bool>) -> UiNode {
141 let skip = skip.into_var();
142 match_node(child, move |_, op| match op {
143 UiNodeOp::Init => {
144 WIDGET.sub_var_info(&skip);
145 }
146 UiNodeOp::Info { info } => {
147 if skip.get() {
148 info.flag_meta(*COLLAPSE_SKIP_ID);
149 }
150 }
151 _ => {}
152 })
153}
154
155bitflags::bitflags! {
156 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
160 pub struct CollapseMode: u8 {
161 const TRIM_START = 0b0000_0001;
163 const TRIM_END = 0b0000_0010;
165 const TRIM = Self::TRIM_START.bits() | Self::TRIM_END.bits();
167
168 const MERGE = 0b0001_0000;
170 }
171}
172impl_from_and_into_var! {
173 fn from(all: bool) -> CollapseMode {
174 if all { CollapseMode::all() } else { CollapseMode::empty() }
175 }
176}
177
178#[allow(non_camel_case_types)]
186pub struct COLLAPSE_SCOPE;
187impl COLLAPSE_SCOPE {
188 pub fn scope_id(&self) -> Option<WidgetId> {
190 SCOPE.get().scope_id
191 }
192
193 pub fn collapse(&self, line_id: WidgetId) -> bool {
195 let scope = SCOPE.get();
196 scope.collapse.contains(&line_id)
197 }
198}
199
200static_id! {
201 pub static ref COLLAPSABLE_LINE_ID: StateId<()>;
205
206 pub static ref COLLAPSE_SKIP_ID: StateId<()>;
210}
211
212context_local! {
213 static SCOPE: CollapseScope = CollapseScope {
214 collapse: IdSet::new(),
215 scope_id: None,
216 };
217}
218
219struct CollapseScope {
220 collapse: IdSet<WidgetId>,
221 scope_id: Option<WidgetId>,
222}
223
224impl CollapseScope {
225 fn new(scope_id: WidgetId) -> Self {
226 Self {
227 collapse: IdSet::new(),
228 scope_id: Some(scope_id),
229 }
230 }
231}