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 Around,
14 Between,
16 End,
18 Start,
20}
21
22#[derive(Debug, Copy, Clone)]
23pub enum ItemAlignment {
24 None,
26 Fill,
28 Center,
30 Left,
33 Right,
36 Top,
39 Bottom,
42}
43
44#[derive(Debug, Copy, Clone)]
45pub struct LinearLayoutSettings {
46 pub orientation: Orientation,
48 pub spacing: Spacing,
50 pub item_align: ItemAlignment,
52 pub fill_equal: bool,
54 pub padding: f32,
56}
57
58impl LinearLayoutSettings {
59
60 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}