termwiz/widgets/
layout.rs

1//! This module provides some automatic layout functionality for widgets.
2//! The parameters are similar to those that you may have encountered
3//! in HTML, but do not fully recreate the layout model.
4use crate::{format_err, Error, Result};
5use cassowary::strength::{REQUIRED, STRONG, WEAK};
6use cassowary::WeightedRelation::*;
7use cassowary::{AddConstraintError, Expression, Solver, SuggestValueError, Variable};
8use std::collections::HashMap;
9
10use crate::widgets::{Rect, WidgetId};
11
12/// Expands to an Expression holding the value of the variable,
13/// or if there is no variable, a constant with the specified
14/// value.
15/// Equivalent to Option::unwrap_or().
16fn unwrap_variable_or(var: Option<Variable>, value: f64) -> Expression {
17    // The `v + 0.0` portion "converts" the variable to an Expression.
18    var.map(|v| v + 0.0)
19        .unwrap_or_else(|| Expression::from_constant(value))
20}
21
22/// Specify whether a width or a height has a preferred fixed size
23/// or whether it should occupy a percentage of its parent container.
24/// The default is 100% of the parent container.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum DimensionSpec {
27    /// Occupy a fixed number of cells
28    Fixed(u16),
29    /// Occupy a percentage of the space in the parent container
30    Percentage(u8),
31}
32
33impl Default for DimensionSpec {
34    fn default() -> Self {
35        DimensionSpec::Percentage(100)
36    }
37}
38
39/// Specifies the extent of a width or height.  The `spec` field
40/// holds the preferred size, while the `minimum` and `maximum`
41/// fields set optional lower and upper bounds.
42#[derive(Clone, Default, Copy, Debug, PartialEq, Eq)]
43pub struct Dimension {
44    pub spec: DimensionSpec,
45    pub maximum: Option<u16>,
46    pub minimum: Option<u16>,
47}
48
49/// Specifies whether the children of a widget are laid out
50/// vertically (top to bottom) or horizontally (left to right).
51/// The default is horizontal.
52#[derive(Clone, Copy, Debug, PartialEq, Eq)]
53pub enum ChildOrientation {
54    Vertical,
55    Horizontal,
56}
57
58impl Default for ChildOrientation {
59    fn default() -> Self {
60        ChildOrientation::Horizontal
61    }
62}
63
64/// Specifies whether the widget should be aligned to the top,
65/// middle or bottom of the vertical space in its parent.
66/// The default is Top.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum VerticalAlignment {
69    Top,
70    Middle,
71    Bottom,
72}
73
74impl Default for VerticalAlignment {
75    fn default() -> Self {
76        VerticalAlignment::Top
77    }
78}
79
80/// Specifies whether the widget should be aligned to the left,
81/// center or right of the horizontal space in its parent.
82/// The default is Left.
83#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84pub enum HorizontalAlignment {
85    Left,
86    Center,
87    Right,
88}
89
90impl Default for HorizontalAlignment {
91    fn default() -> Self {
92        HorizontalAlignment::Left
93    }
94}
95
96/// Specifies the size constraints for a widget
97#[derive(Clone, Default, Copy, Debug, PartialEq, Eq)]
98pub struct Constraints {
99    pub width: Dimension,
100    pub height: Dimension,
101    pub valign: VerticalAlignment,
102    pub halign: HorizontalAlignment,
103    pub child_orientation: ChildOrientation,
104}
105
106impl Constraints {
107    pub fn with_fixed_width_height(width: u16, height: u16) -> Self {
108        *Self::default()
109            .set_fixed_width(width)
110            .set_fixed_height(height)
111    }
112
113    pub fn set_fixed_width(&mut self, width: u16) -> &mut Self {
114        self.width = Dimension {
115            spec: DimensionSpec::Fixed(width),
116            minimum: Some(width),
117            maximum: Some(width),
118        };
119        self
120    }
121
122    pub fn set_pct_width(&mut self, width: u8) -> &mut Self {
123        self.width = Dimension {
124            spec: DimensionSpec::Percentage(width),
125            ..Default::default()
126        };
127        self
128    }
129
130    pub fn set_fixed_height(&mut self, height: u16) -> &mut Self {
131        self.height = Dimension {
132            spec: DimensionSpec::Fixed(height),
133            minimum: Some(height),
134            maximum: Some(height),
135        };
136        self
137    }
138
139    pub fn set_pct_height(&mut self, height: u8) -> &mut Self {
140        self.height = Dimension {
141            spec: DimensionSpec::Percentage(height),
142            ..Default::default()
143        };
144        self
145    }
146
147    pub fn set_valign(&mut self, valign: VerticalAlignment) -> &mut Self {
148        self.valign = valign;
149        self
150    }
151
152    pub fn set_halign(&mut self, halign: HorizontalAlignment) -> &mut Self {
153        self.halign = halign;
154        self
155    }
156}
157
158/// Holds state used to compute the layout of a tree of widgets
159pub struct LayoutState {
160    solver: Solver,
161    screen_width: Variable,
162    screen_height: Variable,
163    widget_states: HashMap<WidgetId, WidgetState>,
164}
165
166/// Each `WidgetId` has a `WidgetState` associated with it.
167/// This allows us to look up the `Variable` for each of the
168/// layout features of a widget by `WidgetId` after the solver
169/// has computed the layout solution.
170#[derive(Clone)]
171struct WidgetState {
172    left: Variable,
173    top: Variable,
174    width: Variable,
175    height: Variable,
176    constraints: Constraints,
177    children: Vec<WidgetId>,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct LaidOutWidget {
182    pub widget: WidgetId,
183    pub rect: Rect,
184}
185
186fn suggesterr(e: SuggestValueError) -> Error {
187    match e {
188        SuggestValueError::UnknownEditVariable => format_err!("Unknown edit variable"),
189        SuggestValueError::InternalSolverError(e) => format_err!("Internal solver error: {}", e),
190    }
191}
192
193fn adderr(e: AddConstraintError) -> Error {
194    format_err!("{:?}", e)
195}
196
197impl Default for LayoutState {
198    fn default() -> Self {
199        Self::new()
200    }
201}
202
203impl LayoutState {
204    /// Create a new `LayoutState`
205    pub fn new() -> Self {
206        let mut solver = Solver::new();
207        let screen_width = Variable::new();
208        let screen_height = Variable::new();
209        solver
210            .add_edit_variable(screen_width, STRONG)
211            .expect("failed to add screen_width to solver");
212        solver
213            .add_edit_variable(screen_height, STRONG)
214            .expect("failed to add screen_height to solver");
215        Self {
216            solver,
217            screen_width,
218            screen_height,
219            widget_states: HashMap::new(),
220        }
221    }
222
223    /// Creates a WidgetState entry for a widget.
224    pub fn add_widget(
225        &mut self,
226        widget: WidgetId,
227        constraints: &Constraints,
228        children: &[WidgetId],
229    ) {
230        let state = WidgetState {
231            left: Variable::new(),
232            top: Variable::new(),
233            width: Variable::new(),
234            height: Variable::new(),
235            constraints: *constraints,
236            children: children.to_vec(),
237        };
238        self.widget_states.insert(widget, state);
239    }
240
241    /// Assign the screen dimensions, compute constraints, solve
242    /// the layout and then apply the size and positioning information
243    /// to the widgets in the widget tree.
244    pub fn compute_constraints(
245        &mut self,
246        screen_width: usize,
247        screen_height: usize,
248        root_widget: WidgetId,
249    ) -> Result<Vec<LaidOutWidget>> {
250        self.solver
251            .suggest_value(self.screen_width, screen_width as f64)
252            .map_err(suggesterr)?;
253        self.solver
254            .suggest_value(self.screen_height, screen_height as f64)
255            .map_err(suggesterr)?;
256
257        let width = self.screen_width;
258        let height = self.screen_height;
259        self.update_widget_constraint(root_widget, width, height, None, None)?;
260
261        // The updates are in an unspecified order, and the coordinates are in
262        // the screen absolute coordinate space, rather than the parent-relative
263        // coordinates that we desire.  So we need to either to accumulate the
264        // deltas and then sort them such that we walk the widget tree to apply
265        // them, or just walk the tree and apply them all anyway.  The latter
266        // feels easier and probably has a low enough cardinality that it won't
267        // feel too expensive.
268        self.solver.fetch_changes();
269
270        let mut results = Vec::new();
271        self.compute_widget_state(root_widget, 0, 0, &mut results)?;
272
273        Ok(results)
274    }
275
276    fn compute_widget_state(
277        &self,
278        widget: WidgetId,
279        parent_left: usize,
280        parent_top: usize,
281        results: &mut Vec<LaidOutWidget>,
282    ) -> Result<()> {
283        let state = self
284            .widget_states
285            .get(&widget)
286            .ok_or_else(|| format_err!("widget has no solver state"))?;
287        let width = self.solver.get_value(state.width) as usize;
288        let height = self.solver.get_value(state.height) as usize;
289        let left = self.solver.get_value(state.left) as usize;
290        let top = self.solver.get_value(state.top) as usize;
291
292        results.push(LaidOutWidget {
293            widget,
294            rect: Rect {
295                x: left - parent_left,
296                y: top - parent_top,
297                width,
298                height,
299            },
300        });
301
302        for child in &state.children {
303            self.compute_widget_state(*child, left, top, results)?;
304        }
305
306        Ok(())
307    }
308
309    fn update_widget_constraint(
310        &mut self,
311        widget: WidgetId,
312        parent_width: Variable,
313        parent_height: Variable,
314        parent_left: Option<Variable>,
315        parent_top: Option<Variable>,
316    ) -> Result<WidgetState> {
317        let state = self
318            .widget_states
319            .get(&widget)
320            .ok_or_else(|| format_err!("widget has no solver state"))?
321            .clone();
322
323        let is_root_widget = parent_left.is_none();
324
325        let parent_left = unwrap_variable_or(parent_left, 0.0);
326        let parent_top = unwrap_variable_or(parent_top, 0.0);
327
328        // First, we should fit inside the parent container
329        self.solver
330            .add_constraint(
331                (state.left + state.width) | LE(REQUIRED) | (parent_left.clone() + parent_width),
332            )
333            .map_err(adderr)?;
334        self.solver
335            .add_constraint(state.left | GE(REQUIRED) | parent_left)
336            .map_err(adderr)?;
337
338        self.solver
339            .add_constraint(
340                (state.top + state.height) | LE(REQUIRED) | (parent_top.clone() + parent_height),
341            )
342            .map_err(adderr)?;
343        self.solver
344            .add_constraint(state.top | GE(REQUIRED) | parent_top)
345            .map_err(adderr)?;
346
347        if is_root_widget {
348            // We handle alignment on the root widget specially here;
349            // for non-root widgets, we handle it when assessing children
350            match state.constraints.halign {
351                HorizontalAlignment::Left => self
352                    .solver
353                    .add_constraint(state.left | EQ(STRONG) | 0.0)
354                    .map_err(adderr)?,
355                HorizontalAlignment::Right => self
356                    .solver
357                    .add_constraint(state.left | EQ(STRONG) | (parent_width - state.width))
358                    .map_err(adderr)?,
359                HorizontalAlignment::Center => self
360                    .solver
361                    .add_constraint(state.left | EQ(STRONG) | ((parent_width - state.width) / 2.0))
362                    .map_err(adderr)?,
363            }
364
365            match state.constraints.valign {
366                VerticalAlignment::Top => self
367                    .solver
368                    .add_constraint(state.top | EQ(STRONG) | 0.0)
369                    .map_err(adderr)?,
370                VerticalAlignment::Bottom => self
371                    .solver
372                    .add_constraint(state.top | EQ(STRONG) | (parent_height - state.height))
373                    .map_err(adderr)?,
374                VerticalAlignment::Middle => self
375                    .solver
376                    .add_constraint(state.top | EQ(STRONG) | ((parent_height - state.height) / 2.0))
377                    .map_err(adderr)?,
378            }
379        }
380
381        match state.constraints.width.spec {
382            DimensionSpec::Fixed(width) => {
383                self.solver
384                    .add_constraint(state.width | EQ(STRONG) | f64::from(width))
385                    .map_err(adderr)?;
386            }
387            DimensionSpec::Percentage(pct) => {
388                self.solver
389                    .add_constraint(
390                        state.width | EQ(STRONG) | (f64::from(pct) * parent_width / 100.0),
391                    )
392                    .map_err(adderr)?;
393            }
394        }
395        self.solver
396            .add_constraint(
397                state.width
398                    | GE(STRONG)
399                    | f64::from(state.constraints.width.minimum.unwrap_or(1).max(1)),
400            )
401            .map_err(adderr)?;
402        if let Some(max_width) = state.constraints.width.maximum {
403            self.solver
404                .add_constraint(state.width | LE(STRONG) | f64::from(max_width))
405                .map_err(adderr)?;
406        }
407
408        match state.constraints.height.spec {
409            DimensionSpec::Fixed(height) => {
410                self.solver
411                    .add_constraint(state.height | EQ(STRONG) | f64::from(height))
412                    .map_err(adderr)?;
413            }
414            DimensionSpec::Percentage(pct) => {
415                self.solver
416                    .add_constraint(
417                        state.height | EQ(STRONG) | (f64::from(pct) * parent_height / 100.0),
418                    )
419                    .map_err(adderr)?;
420            }
421        }
422        self.solver
423            .add_constraint(
424                state.height
425                    | GE(STRONG)
426                    | f64::from(state.constraints.height.minimum.unwrap_or(1).max(1)),
427            )
428            .map_err(adderr)?;
429        if let Some(max_height) = state.constraints.height.maximum {
430            self.solver
431                .add_constraint(state.height | LE(STRONG) | f64::from(max_height))
432                .map_err(adderr)?;
433        }
434
435        let has_children = !state.children.is_empty();
436        if has_children {
437            let mut left_edge: Expression = state.left + 0.0;
438            let mut top_edge: Expression = state.top + 0.0;
439            let mut width_constraint = Expression::from_constant(0.0);
440            let mut height_constraint = Expression::from_constant(0.0);
441
442            for child in &state.children {
443                let child_state = self.update_widget_constraint(
444                    *child,
445                    state.width,
446                    state.height,
447                    Some(state.left),
448                    Some(state.top),
449                )?;
450
451                match child_state.constraints.halign {
452                    HorizontalAlignment::Left => self
453                        .solver
454                        .add_constraint(child_state.left | EQ(STRONG) | left_edge.clone())
455                        .map_err(adderr)?,
456                    HorizontalAlignment::Right => self
457                        .solver
458                        .add_constraint(
459                            (child_state.left + child_state.width)
460                                | EQ(STRONG)
461                                | (state.left + state.width),
462                        )
463                        .map_err(adderr)?,
464                    HorizontalAlignment::Center => self
465                        .solver
466                        .add_constraint(
467                            child_state.left
468                                | EQ(STRONG)
469                                | (state.left + (state.width - child_state.width) / 2.0),
470                        )
471                        .map_err(adderr)?,
472                }
473
474                match child_state.constraints.valign {
475                    VerticalAlignment::Top => self
476                        .solver
477                        .add_constraint(child_state.top | EQ(STRONG) | top_edge.clone())
478                        .map_err(adderr)?,
479                    VerticalAlignment::Bottom => self
480                        .solver
481                        .add_constraint(
482                            (child_state.top + child_state.height)
483                                | EQ(STRONG)
484                                | (state.top + state.height),
485                        )
486                        .map_err(adderr)?,
487                    VerticalAlignment::Middle => self
488                        .solver
489                        .add_constraint(
490                            child_state.top
491                                | EQ(STRONG)
492                                | (state.top + (state.height - child_state.height) / 2.0),
493                        )
494                        .map_err(adderr)?,
495                }
496
497                match state.constraints.child_orientation {
498                    ChildOrientation::Horizontal => {
499                        left_edge = child_state.left + child_state.width;
500                        width_constraint = width_constraint + child_state.width;
501                    }
502                    ChildOrientation::Vertical => {
503                        top_edge = child_state.top + child_state.height;
504                        height_constraint = height_constraint + child_state.height;
505                    }
506                }
507            }
508
509            // This constraint encourages the contents to fill out to the width
510            // of the container, rather than clumping left
511            self.solver
512                .add_constraint(left_edge | EQ(STRONG) | (state.left + state.width))
513                .map_err(adderr)?;
514
515            self.solver
516                .add_constraint(state.width | GE(WEAK) | width_constraint)
517                .map_err(adderr)?;
518
519            // This constraint encourages the contents to fill out to the height
520            // of the container, rather than clumping top
521            self.solver
522                .add_constraint(top_edge | EQ(STRONG) | (state.top + state.height))
523                .map_err(adderr)?;
524
525            self.solver
526                .add_constraint(state.height | GE(WEAK) | height_constraint)
527                .map_err(adderr)?;
528        }
529
530        Ok(state)
531    }
532}
533
534#[cfg(test)]
535mod test {
536    use super::*;
537
538    #[test]
539    fn single_widget_unspec() {
540        let mut layout = LayoutState::new();
541        let main_id = WidgetId::new();
542        layout.add_widget(main_id, &Constraints::default(), &[]);
543        let results = layout.compute_constraints(40, 12, main_id).unwrap();
544
545        assert_eq!(
546            results,
547            vec![LaidOutWidget {
548                widget: main_id,
549                rect: Rect {
550                    x: 0,
551                    y: 0,
552                    width: 40,
553                    height: 12,
554                },
555            }]
556        );
557    }
558
559    #[test]
560    fn two_children_pct() {
561        let root = WidgetId::new();
562        let a = WidgetId::new();
563        let b = WidgetId::new();
564
565        let mut layout = LayoutState::new();
566        layout.add_widget(root, &Constraints::default(), &[a, b]);
567        layout.add_widget(a, Constraints::default().set_pct_width(50), &[]);
568        layout.add_widget(b, Constraints::default().set_pct_width(50), &[]);
569
570        let results = layout.compute_constraints(100, 100, root).unwrap();
571
572        assert_eq!(
573            results,
574            vec![
575                LaidOutWidget {
576                    widget: root,
577                    rect: Rect {
578                        x: 0,
579                        y: 0,
580                        width: 100,
581                        height: 100,
582                    },
583                },
584                LaidOutWidget {
585                    widget: a,
586                    rect: Rect {
587                        x: 0,
588                        y: 0,
589                        width: 50,
590                        height: 100,
591                    },
592                },
593                LaidOutWidget {
594                    widget: b,
595                    rect: Rect {
596                        x: 50,
597                        y: 0,
598                        width: 50,
599                        height: 100,
600                    },
601                },
602            ]
603        );
604    }
605
606    #[test]
607    fn three_children_pct() {
608        let root = WidgetId::new();
609        let a = WidgetId::new();
610        let b = WidgetId::new();
611        let c = WidgetId::new();
612
613        let mut layout = LayoutState::new();
614        layout.add_widget(root, &Constraints::default(), &[a, b, c]);
615        layout.add_widget(a, Constraints::default().set_pct_width(20), &[]);
616        layout.add_widget(b, Constraints::default().set_pct_width(20), &[]);
617        layout.add_widget(c, Constraints::default().set_pct_width(20), &[]);
618
619        let results = layout.compute_constraints(100, 100, root).unwrap();
620
621        assert_eq!(
622            results,
623            vec![
624                LaidOutWidget {
625                    widget: root,
626                    rect: Rect {
627                        x: 0,
628                        y: 0,
629                        width: 100,
630                        height: 100,
631                    },
632                },
633                LaidOutWidget {
634                    widget: a,
635                    rect: Rect {
636                        x: 0,
637                        y: 0,
638                        width: 20,
639                        height: 100,
640                    },
641                },
642                LaidOutWidget {
643                    widget: b,
644                    rect: Rect {
645                        x: 20,
646                        y: 0,
647                        width: 20,
648                        height: 100,
649                    },
650                },
651                LaidOutWidget {
652                    widget: c,
653                    rect: Rect {
654                        x: 40,
655                        y: 0,
656                        width: 20,
657                        height: 100,
658                    },
659                },
660            ]
661        );
662    }
663
664    #[test]
665    fn two_children_a_b() {
666        let root = WidgetId::new();
667        let a = WidgetId::new();
668        let b = WidgetId::new();
669
670        let mut layout = LayoutState::new();
671        layout.add_widget(root, &Constraints::default(), &[a, b]);
672        layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
673        layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
674
675        let results = layout.compute_constraints(100, 100, root).unwrap();
676
677        assert_eq!(
678            results,
679            vec![
680                LaidOutWidget {
681                    widget: root,
682                    rect: Rect {
683                        x: 0,
684                        y: 0,
685                        width: 100,
686                        height: 100,
687                    },
688                },
689                LaidOutWidget {
690                    widget: a,
691                    rect: Rect {
692                        x: 0,
693                        y: 0,
694                        width: 5,
695                        height: 2,
696                    },
697                },
698                LaidOutWidget {
699                    widget: b,
700                    rect: Rect {
701                        x: 5,
702                        y: 0,
703                        width: 3,
704                        height: 2,
705                    },
706                },
707            ]
708        );
709    }
710
711    #[test]
712    fn two_children_b_a() {
713        let root = WidgetId::new();
714        let a = WidgetId::new();
715        let b = WidgetId::new();
716
717        let mut layout = LayoutState::new();
718        layout.add_widget(root, &Constraints::default(), &[b, a]);
719        layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
720        layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
721
722        let results = layout.compute_constraints(100, 100, root).unwrap();
723
724        assert_eq!(
725            results,
726            vec![
727                LaidOutWidget {
728                    widget: root,
729                    rect: Rect {
730                        x: 0,
731                        y: 0,
732                        width: 100,
733                        height: 100,
734                    },
735                },
736                LaidOutWidget {
737                    widget: b,
738                    rect: Rect {
739                        x: 0,
740                        y: 0,
741                        width: 3,
742                        height: 2,
743                    },
744                },
745                LaidOutWidget {
746                    widget: a,
747                    rect: Rect {
748                        x: 3,
749                        y: 0,
750                        width: 5,
751                        height: 2,
752                    },
753                },
754            ]
755        );
756    }
757
758    #[test]
759    fn two_children_overflow() {
760        let root = WidgetId::new();
761        let a = WidgetId::new();
762        let b = WidgetId::new();
763
764        let mut layout = LayoutState::new();
765        layout.add_widget(root, &Constraints::default(), &[a, b]);
766        layout.add_widget(a, &Constraints::with_fixed_width_height(5, 2), &[]);
767        layout.add_widget(b, &Constraints::with_fixed_width_height(3, 2), &[]);
768
769        let results = layout.compute_constraints(6, 2, root).unwrap();
770
771        assert_eq!(
772            results,
773            vec![
774                LaidOutWidget {
775                    widget: root,
776                    rect: Rect {
777                        x: 0,
778                        y: 0,
779                        width: 8,
780                        height: 2,
781                    },
782                },
783                LaidOutWidget {
784                    widget: a,
785                    rect: Rect {
786                        x: 0,
787                        y: 0,
788                        width: 5,
789                        height: 2,
790                    },
791                },
792                LaidOutWidget {
793                    widget: b,
794                    rect: Rect {
795                        x: 5,
796                        y: 0,
797                        width: 3,
798                        height: 2,
799                    },
800                },
801            ]
802        );
803    }
804
805    macro_rules! single_constrain {
806        ($name:ident, $constraint:expr, $width:expr, $height:expr, $x:expr, $y:expr) => {
807            #[test]
808            fn $name() {
809                let root = WidgetId::new();
810                let mut layout = LayoutState::new();
811                layout.add_widget(root, &$constraint, &[]);
812
813                let results = layout.compute_constraints(100, 100, root).unwrap();
814
815                assert_eq!(
816                    results,
817                    vec![LaidOutWidget {
818                        widget: root,
819                        rect: Rect {
820                            x: $x,
821                            y: $y,
822                            width: $width,
823                            height: $height,
824                        },
825                    }]
826                );
827            }
828        };
829    }
830
831    single_constrain!(
832        single_constrained_widget_top,
833        Constraints::with_fixed_width_height(10, 2),
834        10,
835        2,
836        0,
837        0
838    );
839
840    single_constrain!(
841        single_constrained_widget_bottom,
842        Constraints::with_fixed_width_height(10, 2)
843            .set_valign(VerticalAlignment::Bottom)
844            .clone(),
845        10,
846        2,
847        0,
848        98
849    );
850
851    single_constrain!(
852        single_constrained_widget_middle,
853        Constraints::with_fixed_width_height(10, 2)
854            .set_valign(VerticalAlignment::Middle)
855            .clone(),
856        10,
857        2,
858        0,
859        49
860    );
861
862    single_constrain!(
863        single_constrained_widget_right,
864        Constraints::with_fixed_width_height(10, 2)
865            .set_halign(HorizontalAlignment::Right)
866            .clone(),
867        10,
868        2,
869        90,
870        0
871    );
872
873    single_constrain!(
874        single_constrained_widget_bottom_center,
875        Constraints::with_fixed_width_height(10, 2)
876            .set_valign(VerticalAlignment::Bottom)
877            .set_halign(HorizontalAlignment::Center)
878            .clone(),
879        10,
880        2,
881        45,
882        98
883    );
884}