zng_wgt/
layout_props.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
use std::fmt;

use zng_layout::context::DIRECTION_VAR;

use crate::prelude::*;

/// Margin space around the widget.
///
/// This property adds side offsets to the widget inner visual, it will be combined with the other
/// layout properties of the widget to define the inner visual position and widget size.
///
/// This property disables inline layout for the widget.
#[property(LAYOUT, default(0))]
pub fn margin(child: impl UiNode, margin: impl IntoVar<SideOffsets>) -> impl UiNode {
    let margin = margin.into_var();
    match_node(child, move |child, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_var_layout(&margin);
        }
        UiNodeOp::Measure { wm, desired_size } => {
            let margin = margin.layout();
            let size_increment = PxSize::new(margin.horizontal(), margin.vertical());
            *desired_size = LAYOUT.with_constraints(LAYOUT.constraints().with_less_size(size_increment), || wm.measure_block(child));
            desired_size.width += size_increment.width;
            desired_size.height += size_increment.height;
        }
        UiNodeOp::Layout { wl, final_size } => {
            let margin = margin.layout();
            let size_increment = PxSize::new(margin.horizontal(), margin.vertical());

            *final_size = LAYOUT.with_constraints(LAYOUT.constraints().with_less_size(size_increment), || child.layout(wl));
            let mut translate = PxVector::zero();
            final_size.width += size_increment.width;
            translate.x = margin.left;
            final_size.height += size_increment.height;
            translate.y = margin.top;
            wl.translate(translate);
        }
        _ => {}
    })
}

/// Aligns the widget within the available space.
///
/// This property disables inline layout for the widget.
///
/// See [`Align`] for more details.
///
///  [`Align`]: zng_layout::unit::Align
#[property(LAYOUT, default(Align::FILL))]
pub fn align(child: impl UiNode, alignment: impl IntoVar<Align>) -> impl UiNode {
    let alignment = alignment.into_var();
    match_node(child, move |child, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_var_layout(&alignment);
        }
        UiNodeOp::Measure { wm, desired_size } => {
            let align = alignment.get();
            let child_size = LAYOUT.with_constraints(align.child_constraints(LAYOUT.constraints()), || wm.measure_block(child));
            *desired_size = align.measure(child_size, LAYOUT.constraints());
        }
        UiNodeOp::Layout { wl, final_size } => {
            let align = alignment.get();
            let child_size = LAYOUT.with_constraints(align.child_constraints(LAYOUT.constraints()), || child.layout(wl));
            let (size, offset, baseline) = align.layout(child_size, LAYOUT.constraints(), LAYOUT.direction());
            wl.translate(offset);
            if baseline {
                wl.translate_baseline(true);
            }
            *final_size = size;
        }
        _ => {}
    })
}

/// If the layout direction is right-to-left.
///
/// The `state` is bound to [`DIRECTION_VAR`].
///
/// [`DIRECTION_VAR`]: zng_layout::context::DIRECTION_VAR
#[property(LAYOUT)]
pub fn is_rtl(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
    bind_state(child, DIRECTION_VAR.map(|s| s.is_rtl()), state)
}

/// If the layout direction is left-to-right.
///
/// The `state` is bound to [`DIRECTION_VAR`].
///
/// [`DIRECTION_VAR`]: zng_layout::context::DIRECTION_VAR
#[property(LAYOUT)]
pub fn is_ltr(child: impl UiNode, state: impl IntoVar<bool>) -> impl UiNode {
    bind_state(child, DIRECTION_VAR.map(|s| s.is_ltr()), state)
}

/// Inline mode explicitly selected for a widget.
#[derive(Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum InlineMode {
    /// Widget does inline if requested by the parent widget layout and is composed only of properties that support inline.
    ///
    /// This is the default behavior.
    #[default]
    Allow,
    /// Widget always does inline.
    ///
    /// If the parent layout does not setup an inline layout environment the widget itself will. This
    /// can be used to force the inline visual, such as background clipping or any other special visual
    /// that is only enabled when the widget is inlined.
    ///
    /// Note that the widget will only inline if composed only of properties that support inline.
    Inline,
    /// Widget disables inline.
    ///
    /// If the parent widget requests inline the request does not propagate for child nodes and
    /// inline is disabled on the widget.
    Block,
}
impl fmt::Debug for InlineMode {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            write!(f, "InlineMode::")?;
        }
        match self {
            Self::Allow => write!(f, "Allow"),
            Self::Inline => write!(f, "Inline"),
            Self::Block => write!(f, "Block"),
        }
    }
}
impl_from_and_into_var! {
    fn from(inline: bool) -> InlineMode {
        if inline {
            InlineMode::Inline
        } else {
            InlineMode::Block
        }
    }
}

/// Enforce an inline mode on the widget.
///
/// See [`InlineMode`] for more details.
#[property(WIDGET, default(InlineMode::Allow))]
pub fn inline(child: impl UiNode, mode: impl IntoVar<InlineMode>) -> impl UiNode {
    let mode = mode.into_var();
    match_node(child, move |child, op| match op {
        UiNodeOp::Init => {
            WIDGET.sub_var_layout(&mode);
        }
        UiNodeOp::Measure { wm, desired_size } => {
            *desired_size = match mode.get() {
                InlineMode::Allow => child.measure(wm),
                InlineMode::Inline => {
                    if LAYOUT.inline_constraints().is_none() {
                        // enable inline for content.
                        wm.with_inline_visual(|wm| child.measure(wm))
                    } else {
                        // already enabled by parent
                        child.measure(wm)
                    }
                }
                InlineMode::Block => {
                    // disable inline, method also disables in `WidgetMeasure`
                    wm.measure_block(child)
                }
            };
        }
        UiNodeOp::Layout { wl, final_size } => {
            *final_size = match mode.get() {
                InlineMode::Allow => child.layout(wl),
                InlineMode::Inline => {
                    if LAYOUT.inline_constraints().is_none() {
                        wl.to_measure(None).with_inline_visual(|wm| child.measure(wm));
                        wl.with_inline_visual(|wl| child.layout(wl))
                    } else {
                        // already enabled by parent
                        child.layout(wl)
                    }
                }
                InlineMode::Block => {
                    if wl.inline().is_some() {
                        tracing::error!("inline enabled in `layout` when it signaled disabled in the previous `measure`");
                        wl.layout_block(child)
                    } else {
                        child.layout(wl)
                    }
                }
            };
        }
        _ => {}
    })
}

context_var! {
    /// Variable that indicates the context should use mobile UI themes.
    ///
    /// This is `true` by default in Android and iOS builds. It can also be set using [`force_mobile`](fn@force_mobile).
    pub static IS_MOBILE_VAR: bool = cfg!(any(target_os = "android", target_os = "ios"));
}

/// Requests mobile UI themes in desktop builds.
///
/// This property sets the [`IS_MOBILE_VAR`].
#[property(CONTEXT, default(IS_MOBILE_VAR))]
pub fn force_mobile(child: impl UiNode, is_mobile: impl IntoVar<bool>) -> impl UiNode {
    with_context_var(child, IS_MOBILE_VAR, is_mobile)
}

/// Gets the [`IS_MOBILE_VAR`] that indicates the window or widget should use mobile UI themes.
#[property(EVENT)]
pub fn is_mobile(child: impl UiNode, is_mobile: impl IntoVar<bool>) -> impl UiNode {
    zng_wgt::node::bind_state(child, IS_MOBILE_VAR, is_mobile)
}