limn_layout/
linear_layout.rs

1use std::collections::HashMap;
2
3use cassowary::strength::*;
4use cassowary::WeightedRelation::*;
5use cassowary::{Variable, Constraint};
6
7use super::{LayoutId, LayoutVars, Layout, LayoutContainer};
8use super::constraint::*;
9
10#[derive(Debug, Copy, Clone,PartialEq)]
11pub enum Spacing {
12    /// Equal spacing before, after and between all elements
13    Around,
14    /// Equal spacing between elements, with no extra space on the ends
15    Between,
16    /// Elements packed together at the start, leaving space at the end
17    End,
18    /// Elements packed together at the end, leaving space at the start
19    Start,
20}
21
22#[derive(Debug, Copy, Clone)]
23pub enum ItemAlignment {
24    /// No constraints are added, items can be aligned individually
25    None,
26    /// Item width/height matched layout parent
27    Fill,
28    /// Items centered within layout parent width/height
29    Center,
30    /// For a vertical layout, align items to the parent's left bound.
31    /// Do not use in a horizontal layout
32    Left,
33    /// For a vertical layout, align items to the parent's right bound.
34    /// Do not use in a horizontal layout
35    Right,
36    /// For a horizontal layout, align items to the parent's top bound.
37    /// Do not use in a vertical layout
38    Top,
39    /// For a horizontal layout, align items to the parent's bottom bound.
40    /// Do not use in a vertical layout
41    Bottom,
42}
43
44#[derive(Debug, Copy, Clone)]
45pub struct LinearLayoutSettings {
46    /// Horizontal or vertical orientation of the current layout
47    pub orientation: Orientation,
48    /// Specifies the extra space between elements along the primary axis
49    pub spacing: Spacing,
50    /// Specifies alignment of items perpendicular to the primary axis
51    pub item_align: ItemAlignment,
52    /// Constrain items to have equal size along primary axis, and fill container
53    pub fill_equal: bool,
54    /// Minimum spacing between items
55    pub padding: f32,
56}
57
58impl LinearLayoutSettings {
59
60    /// Creates a default `LinearLayoutSettings`, aligned to the
61    /// end of the parent container
62    pub fn new(orientation: Orientation) -> Self {
63        LinearLayoutSettings {
64            orientation: orientation,
65            spacing: Spacing::End,
66            item_align: ItemAlignment::None,
67            fill_equal: false,
68            padding: 0.0,
69        }
70    }
71}
72
73#[derive(Debug, Copy, Clone)]
74pub enum Orientation {
75    Horizontal,
76    Vertical,
77}
78
79#[derive(Debug, Clone)]
80struct WidgetData {
81    start: Variable,
82    end: Variable,
83    prev: Option<LayoutId>,
84    next: Option<LayoutId>,
85    end_constraint: Option<Constraint>,
86}
87
88pub struct LinearLayout {
89    settings: LinearLayoutSettings,
90    start: Variable,
91    end: Variable,
92    space: Variable,
93    size: Option<Variable>,
94    widgets: HashMap<LayoutId, WidgetData>,
95    last_widget: Option<LayoutId>,
96}
97
98impl LinearLayout {
99    pub fn new(parent: &mut Layout, settings: LinearLayoutSettings) -> Self {
100        let start = Variable::new();
101        let end = Variable::new();
102        parent.add_associated_var(start, "linear_layout_start");
103        parent.add_associated_var(end, "linear_layout_end");
104        let space = Variable::new();
105        parent.add_associated_var(space, "linear_layout_space");
106        match settings.spacing {
107            Spacing::Between | Spacing::Around => parent.add(space | GE(REQUIRED) | settings.padding),
108            _ => parent.add(space | EQ(REQUIRED) | settings.padding)
109        };
110        let parent_start = beginning(settings.orientation, &parent.vars);
111        let parent_end = ending(settings.orientation, &parent.vars);
112        match settings.spacing {
113            Spacing::Around => {
114                parent.add(constraints![
115                    start | EQ(REQUIRED) | parent_start + space,
116                    end | EQ(REQUIRED) | parent_end - space,
117                ]);
118            },
119            _ => {
120                parent.add(constraints![
121                    start | EQ(REQUIRED) | parent_start,
122                    end | EQ(REQUIRED) | parent_end,
123                ]);
124            }
125        }
126
127        let size = if settings.fill_equal {
128            let size = Variable::new();
129            parent.add_associated_var(size, "linear_layout_size");
130            let parent_size = axis_length(settings.orientation, &parent.vars);
131            parent.add(parent_size | EQ(STRONG) | size);
132            Some(size)
133        } else {
134            None
135        };
136        LinearLayout {
137            settings: settings,
138            start: start,
139            end: end,
140            space: space,
141            size: size,
142            widgets: HashMap::new(),
143            last_widget: None,
144        }
145    }
146}
147
148impl LayoutContainer for LinearLayout {
149    fn add_child(&mut self, parent: &mut Layout, child: &mut Layout) {
150
151        let child_start = beginning(self.settings.orientation, &child.vars);
152        let child_end = ending(self.settings.orientation, &child.vars);
153
154        parent.add(child_start | GE(REQUIRED) | self.start);
155        parent.add(child_end | LE(REQUIRED) | self.end);
156
157        if let Some(last_id) = self.last_widget {
158            let last_widget = self.widgets.get_mut(&last_id).unwrap();
159            parent.add(child_start | EQ(REQUIRED) | last_widget.end + self.space);
160            last_widget.next = Some(child.id);
161        } else {
162            if self.settings.spacing != Spacing::Start {
163                parent.add(child_start | EQ(REQUIRED) | self.start);
164            }
165        }
166        let end_constraint = {
167            if self.settings.spacing != Spacing::End {
168                if let Some(last_id) = self.last_widget {
169                    let last_widget = self.widgets.get_mut(&last_id).unwrap();
170                    parent.remove_constraint(last_widget.end_constraint.take().unwrap());
171                }
172                let end_constraint = child_end | EQ(REQUIRED) | self.end;
173                parent.add(end_constraint.clone());
174                Some(end_constraint)
175            } else {
176                None
177            }
178        };
179        self.widgets.insert(child.id, WidgetData {
180            start: child_start,
181            end: child_end,
182            prev: self.last_widget,
183            next: None,
184            end_constraint: end_constraint,
185        });
186        self.last_widget = Some(child.id);
187
188        if self.settings.fill_equal {
189            let child_size = axis_length(self.settings.orientation, &child.vars);
190            parent.add(child_size | EQ(REQUIRED) | self.size.unwrap());
191        }
192        match self.settings.orientation {
193            Orientation::Horizontal => {
194                match self.settings.item_align {
195                    ItemAlignment::Fill => {
196                        child.add(constraints![
197                            align_top(parent),
198                            align_bottom(parent),
199                        ]);
200                    },
201                    ItemAlignment::Center => {
202                        child.add(constraints![
203                            center_vertical(parent),
204                            bound_top(parent),
205                            bound_bottom(parent),
206                        ]);
207                    },
208                    ItemAlignment::Top => {
209                        child.add(constraints![
210                            align_top(parent),
211                            bound_bottom(parent),
212                        ]);
213                    },
214                    ItemAlignment::Bottom => {
215                        child.add(constraints![
216                            bound_top(parent),
217                            align_bottom(parent),
218                        ]);
219                    },
220                    ItemAlignment::None => {
221                        child.add(constraints![
222                            bound_top(parent),
223                            bound_bottom(parent),
224                        ]);
225                    },
226                    _ => panic!("Invalid linear layout settings"),
227                }
228            },
229            Orientation::Vertical => {
230                match self.settings.item_align {
231                    ItemAlignment::Fill => {
232                        child.add(constraints![
233                            align_left(parent),
234                            align_right(parent),
235                        ]);
236                    },
237                    ItemAlignment::Center => {
238                        child.add(constraints![
239                            center_horizontal(parent),
240                            bound_left(parent),
241                            bound_right(parent),
242                        ]);
243                    },
244                    ItemAlignment::Left => {
245                        child.add(constraints![
246                            align_left(parent),
247                            bound_right(parent),
248                        ]);
249                    },
250                    ItemAlignment::Right => {
251                        child.add(constraints![
252                            bound_left(parent),
253                            align_right(parent),
254                        ]);
255                    },
256                    ItemAlignment::None => {
257                        child.add(constraints![
258                            bound_left(parent),
259                            bound_right(parent),
260                        ]);
261                    },
262                    _ => panic!("Invalid linear layout settings"),
263                }
264            }
265        }
266    }
267
268    fn remove_child(&mut self, parent: &mut Layout, child: &mut Layout) {
269        if let Some(widget_data) = self.widgets.remove(&child.id) {
270            if let Some(prev) = widget_data.prev {
271                let next_start = widget_data.next.map(|next_id| self.widgets[&next_id].start);
272                let prev = self.widgets.get_mut(&prev).unwrap();
273                if let Some(next_start) = next_start {
274                    parent.add(next_start | EQ(REQUIRED) | prev.end + self.space);
275                } else {
276                    if self.settings.spacing != Spacing::End {
277                        let end_constraint = prev.end | EQ(REQUIRED) | self.end;
278                        parent.add(end_constraint.clone());
279                        prev.end_constraint = Some(end_constraint);
280                    }
281                }
282                prev.next = widget_data.next;
283            } else if let Some(next) = widget_data.next {
284                if self.settings.spacing != Spacing::Start {
285                    let next_start = self.widgets[&next].start;
286                    parent.add(next_start | EQ(REQUIRED) | self.start);
287                }
288            }
289            if let Some(next) = widget_data.next {
290                self.widgets.get_mut(&next).unwrap().prev = widget_data.prev;
291            }
292
293            if let Some(last_id) = self.last_widget {
294                if last_id == child.id {
295                    self.last_widget = widget_data.prev;
296                }
297            }
298        }
299    }
300}
301
302fn beginning(orientation: Orientation, layout: &LayoutVars) -> Variable {
303    match orientation {
304        Orientation::Horizontal => layout.left,
305        Orientation::Vertical => layout.top,
306    }
307}
308fn ending(orientation: Orientation, layout: &LayoutVars) -> Variable {
309    match orientation {
310        Orientation::Horizontal => layout.right,
311        Orientation::Vertical => layout.bottom,
312    }
313}
314fn axis_length(orientation: Orientation, layout: &LayoutVars) -> Variable {
315    match orientation {
316        Orientation::Horizontal => layout.width,
317        Orientation::Vertical => layout.height,
318    }
319}