1use std::fmt;
2
3use zng_layout::context::DIRECTION_VAR;
4
5use crate::prelude::*;
6
7#[property(LAYOUT, default(0))]
14pub fn margin(child: impl IntoUiNode, margin: impl IntoVar<SideOffsets>) -> UiNode {
15 let margin = margin.into_var();
16 match_node(child, move |child, op| match op {
17 UiNodeOp::Init => {
18 WIDGET.sub_var_layout(&margin);
19 }
20 UiNodeOp::Measure { wm, desired_size } => {
21 let margin = margin.layout();
22 let size_increment = PxSize::new(margin.horizontal(), margin.vertical());
23 *desired_size = LAYOUT.with_constraints(LAYOUT.constraints().with_less_size(size_increment), || {
24 wm.measure_block(child.node())
25 });
26 child.delegated();
27 desired_size.width += size_increment.width;
28 desired_size.height += size_increment.height;
29 }
30 UiNodeOp::Layout { wl, final_size } => {
31 let margin = margin.layout();
32 let size_increment = PxSize::new(margin.horizontal(), margin.vertical());
33
34 *final_size = LAYOUT.with_constraints(LAYOUT.constraints().with_less_size(size_increment), || child.layout(wl));
35 let mut translate = PxVector::zero();
36 final_size.width += size_increment.width;
37 translate.x = margin.left;
38 final_size.height += size_increment.height;
39 translate.y = margin.top;
40 wl.translate(translate);
41 }
42 _ => {}
43 })
44}
45
46#[property(LAYOUT, default(Align::FILL))]
54pub fn align(child: impl IntoUiNode, alignment: impl IntoVar<Align>) -> UiNode {
55 let alignment = alignment.into_var();
56 match_node(child, move |child, op| match op {
57 UiNodeOp::Init => {
58 WIDGET.sub_var_layout(&alignment);
59 }
60 UiNodeOp::Measure { wm, desired_size } => {
61 let align = alignment.get();
62 let child_size = LAYOUT.with_constraints(align.child_constraints(LAYOUT.constraints()), || wm.measure_block(child.node()));
63 child.delegated();
64 *desired_size = align.measure(child_size, LAYOUT.constraints());
65 }
66 UiNodeOp::Layout { wl, final_size } => {
67 let align = alignment.get();
68 let child_size = LAYOUT.with_constraints(align.child_constraints(LAYOUT.constraints()), || child.layout(wl));
69 let (size, offset, baseline) = align.layout(child_size, LAYOUT.constraints(), LAYOUT.direction());
70 wl.translate(offset);
71 if baseline {
72 wl.translate_baseline(true);
73 }
74 *final_size = size;
75 }
76 _ => {}
77 })
78}
79
80#[property(LAYOUT)]
86pub fn is_rtl(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
87 bind_state(child, DIRECTION_VAR.map(|s| s.is_rtl()), state)
88}
89
90#[property(LAYOUT)]
96pub fn is_ltr(child: impl IntoUiNode, state: impl IntoVar<bool>) -> UiNode {
97 bind_state(child, DIRECTION_VAR.map(|s| s.is_ltr()), state)
98}
99
100#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
102pub enum InlineMode {
103 #[default]
107 Allow,
108 Inline,
116 Block,
121}
122impl fmt::Debug for InlineMode {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 if f.alternate() {
125 write!(f, "InlineMode::")?;
126 }
127 match self {
128 Self::Allow => write!(f, "Allow"),
129 Self::Inline => write!(f, "Inline"),
130 Self::Block => write!(f, "Block"),
131 }
132 }
133}
134impl_from_and_into_var! {
135 fn from(inline: bool) -> InlineMode {
136 if inline { InlineMode::Inline } else { InlineMode::Block }
137 }
138}
139
140#[property(WIDGET, default(InlineMode::Allow))]
144pub fn inline(child: impl IntoUiNode, mode: impl IntoVar<InlineMode>) -> UiNode {
145 let mode = mode.into_var();
146 match_node(child, move |child, op| match op {
147 UiNodeOp::Init => {
148 WIDGET.sub_var_layout(&mode);
149 }
150 UiNodeOp::Measure { wm, desired_size } => {
151 *desired_size = match mode.get() {
152 InlineMode::Allow => child.measure(wm),
153 InlineMode::Inline => {
154 if LAYOUT.inline_constraints().is_none() {
155 wm.with_inline_visual(|wm| child.measure(wm))
157 } else {
158 child.measure(wm)
160 }
161 }
162 InlineMode::Block => {
163 child.delegated();
165 wm.measure_block(child.node())
166 }
167 };
168 }
169 UiNodeOp::Layout { wl, final_size } => {
170 *final_size = match mode.get() {
171 InlineMode::Allow => child.layout(wl),
172 InlineMode::Inline => {
173 if LAYOUT.inline_constraints().is_none() {
174 wl.to_measure(None).with_inline_visual(|wm| child.measure(wm));
175 wl.with_inline_visual(|wl| child.layout(wl))
176 } else {
177 child.layout(wl)
179 }
180 }
181 InlineMode::Block => {
182 if wl.inline().is_some() {
183 tracing::error!("inline enabled in `layout` when it signaled disabled in the previous `measure`");
184 child.delegated();
185 wl.layout_block(child.node())
186 } else {
187 child.layout(wl)
188 }
189 }
190 };
191 }
192 _ => {}
193 })
194}
195
196context_var! {
197 pub static IS_MOBILE_VAR: bool = cfg!(any(target_os = "android", target_os = "ios"));
201}
202
203#[property(CONTEXT, default(IS_MOBILE_VAR))]
207pub fn force_mobile(child: impl IntoUiNode, is_mobile: impl IntoVar<bool>) -> UiNode {
208 with_context_var(child, IS_MOBILE_VAR, is_mobile)
209}
210
211#[property(EVENT)]
213pub fn is_mobile(child: impl IntoUiNode, is_mobile: impl IntoVar<bool>) -> UiNode {
214 zng_wgt::node::bind_state(child, IS_MOBILE_VAR, is_mobile)
215}