@@ -294,6 +294,13 @@ pub enum CrossAxisAlignment {
294294 /// In a vertical container, widgets are bottom aligned. In a horiziontal
295295 /// container, their trailing edges are aligned.
296296 End ,
297+ /// Align on the baseline.
298+ ///
299+ /// In a horizontal container, widgets are aligned along the calculated
300+ /// baseline. In a vertical container, this is equivalent to `End`.
301+ ///
302+ /// The calculated baseline is the maximum baseline offset of the children.
303+ Baseline ,
297304}
298305
299306/// Arrangement of children on the main axis.
@@ -607,15 +614,21 @@ impl<T: Data> Widget<T> for Flex<T> {
607614 // we loosen our constraints when passing to children.
608615 let loosened_bc = bc. loosen ( ) ;
609616
617+ // minor-axis values for all children
618+ let mut minor = self . direction . minor ( bc. min ( ) ) ;
619+ // these two are calculated but only used if we're baseline aligned
620+ let mut max_above_baseline = 0f64 ;
621+ let mut max_below_baseline = 0f64 ;
622+
610623 // Measure non-flex children.
611624 let mut major_non_flex = 0.0 ;
612- let mut minor = self . direction . minor ( bc. min ( ) ) ;
613625 for child in & mut self . children {
614626 if child. params . flex == 0.0 {
615627 let child_bc = self
616628 . direction
617629 . constraints ( & loosened_bc, 0. , std:: f64:: INFINITY ) ;
618630 let child_size = child. widget . layout ( ctx, & child_bc, data, env) ;
631+ let baseline_offset = child. widget . baseline_offset ( ) ;
619632
620633 if child_size. width . is_infinite ( ) {
621634 log:: warn!( "A non-Flex child has an infinite width." ) ;
@@ -627,6 +640,8 @@ impl<T: Data> Widget<T> for Flex<T> {
627640
628641 major_non_flex += self . direction . major ( child_size) . expand ( ) ;
629642 minor = minor. max ( self . direction . minor ( child_size) . expand ( ) ) ;
643+ max_above_baseline = max_above_baseline. max ( child_size. height - baseline_offset) ;
644+ max_below_baseline = max_below_baseline. max ( baseline_offset) ;
630645 // Stash size.
631646 let rect = child_size. to_rect ( ) ;
632647 child. widget . set_layout_rect ( ctx, data, env, rect) ;
@@ -651,9 +666,12 @@ impl<T: Data> Widget<T> for Flex<T> {
651666 . direction
652667 . constraints ( & loosened_bc, min_major, actual_major) ;
653668 let child_size = child. widget . layout ( ctx, & child_bc, data, env) ;
669+ let baseline_offset = child. widget . baseline_offset ( ) ;
654670
655671 major_flex += self . direction . major ( child_size) . expand ( ) ;
656672 minor = minor. max ( self . direction . minor ( child_size) . expand ( ) ) ;
673+ max_above_baseline = max_above_baseline. max ( child_size. height - baseline_offset) ;
674+ max_below_baseline = max_below_baseline. max ( baseline_offset) ;
657675 // Stash size.
658676 let rect = child_size. to_rect ( ) ;
659677 child. widget . set_layout_rect ( ctx, data, env, rect) ;
@@ -670,21 +688,39 @@ impl<T: Data> Widget<T> for Flex<T> {
670688 } ;
671689
672690 let mut spacing = Spacing :: new ( self . main_alignment , extra, self . children . len ( ) ) ;
673- // Finalize layout, assigning positions to each child.
691+
692+ // the actual size needed to tightly fit the children on the minor axis.
693+ // Unlike the 'minor' var, this ignores the incoming constraints.
694+ let minor_dim = match self . direction {
695+ Axis :: Horizontal => max_below_baseline + max_above_baseline,
696+ Axis :: Vertical => minor,
697+ } ;
698+
674699 let mut major = spacing. next ( ) . unwrap_or ( 0. ) ;
675700 let mut child_paint_rect = Rect :: ZERO ;
676701 for child in & mut self . children {
677- let rect = child. widget . layout_rect ( ) ;
678- let extra_minor = minor - self . direction . minor ( rect. size ( ) ) ;
702+ let child_size = child. widget . layout_rect ( ) . size ( ) ;
679703 let alignment = child. params . alignment . unwrap_or ( self . cross_alignment ) ;
680- let align_minor = alignment. align ( extra_minor) ;
681- let pos: Point = self . direction . pack ( major, align_minor) . into ( ) ;
704+ let child_minor_offset = match alignment {
705+ // This will ignore baseline alignment if it is overridden on children,
706+ // but is not the default for the container. Is this okay?
707+ CrossAxisAlignment :: Baseline if matches ! ( self . direction, Axis :: Horizontal ) => {
708+ let extra_height = minor - minor_dim. min ( minor) ;
709+ let child_baseline = child. widget . baseline_offset ( ) ;
710+ let child_above_baseline = child_size. height - child_baseline;
711+ extra_height + ( max_above_baseline - child_above_baseline)
712+ }
713+ _ => {
714+ let extra_minor = minor_dim - self . direction . minor ( child_size) ;
715+ alignment. align ( extra_minor)
716+ }
717+ } ;
682718
683- child
684- . widget
685- . set_layout_rect ( ctx, data, env, rect . with_origin ( pos ) ) ;
719+ let child_pos : Point = self . direction . pack ( major , child_minor_offset ) . into ( ) ;
720+ let child_frame = Rect :: from_origin_size ( child_pos , child_size ) ;
721+ child . widget . set_layout_rect ( ctx, data, env, child_frame ) ;
686722 child_paint_rect = child_paint_rect. union ( child. widget . paint_rect ( ) ) ;
687- major += self . direction . major ( rect . size ( ) ) . expand ( ) ;
723+ major += self . direction . major ( child_size ) . expand ( ) ;
688724 major += spacing. next ( ) . unwrap_or ( 0. ) ;
689725 }
690726
@@ -696,7 +732,7 @@ impl<T: Data> Widget<T> for Flex<T> {
696732 major = total_major;
697733 }
698734
699- let my_size: Size = self . direction . pack ( major, minor ) . into ( ) ;
735+ let my_size: Size = self . direction . pack ( major, minor_dim ) . into ( ) ;
700736
701737 // if we don't have to fill the main axis, we loosen that axis before constraining
702738 let my_size = if !self . fill_major_axis {
@@ -711,13 +747,38 @@ impl<T: Data> Widget<T> for Flex<T> {
711747 let my_bounds = Rect :: ZERO . with_size ( my_size) ;
712748 let insets = child_paint_rect - my_bounds;
713749 ctx. set_paint_insets ( insets) ;
750+
751+ let baseline_offset = match self . direction {
752+ Axis :: Horizontal => max_below_baseline,
753+ Axis :: Vertical => self
754+ . children
755+ . last ( )
756+ . map ( |last| {
757+ let child_bl = last. widget . baseline_offset ( ) ;
758+ let child_max_y = last. widget . layout_rect ( ) . max_y ( ) ;
759+ let extra_bottom_padding = my_size. height - child_max_y;
760+ child_bl + extra_bottom_padding
761+ } )
762+ . unwrap_or ( 0.0 ) ,
763+ } ;
764+
765+ ctx. set_baseline_offset ( baseline_offset) ;
714766 my_size
715767 }
716768
717769 fn paint ( & mut self , ctx : & mut PaintCtx , data : & T , env : & Env ) {
718770 for child in & mut self . children {
719771 child. widget . paint ( ctx, data, env) ;
720772 }
773+
774+ // paint the baseline if we're debugging layout
775+ if env. get ( Env :: DEBUG_PAINT ) && ctx. widget_state . baseline_offset != 0.0 {
776+ let color = env. get_debug_color ( ctx. widget_id ( ) . to_raw ( ) ) ;
777+ let my_baseline = ctx. size ( ) . height - ctx. widget_state . baseline_offset ;
778+ let line = crate :: kurbo:: Line :: new ( ( 0.0 , my_baseline) , ( ctx. size ( ) . width , my_baseline) ) ;
779+ let stroke_style = crate :: piet:: StrokeStyle :: new ( ) . dash ( vec ! [ 4.0 , 4.0 ] , 0.0 ) ;
780+ ctx. stroke_styled ( line, & color, 1.0 , & stroke_style) ;
781+ }
721782 }
722783}
723784
@@ -728,7 +789,8 @@ impl CrossAxisAlignment {
728789 fn align ( self , val : f64 ) -> f64 {
729790 match self {
730791 CrossAxisAlignment :: Start => 0.0 ,
731- CrossAxisAlignment :: Center => ( val / 2.0 ) . round ( ) ,
792+ // in vertical layout, baseline is equivalent to center
793+ CrossAxisAlignment :: Center | CrossAxisAlignment :: Baseline => ( val / 2.0 ) . round ( ) ,
732794 CrossAxisAlignment :: End => val,
733795 }
734796 }
0 commit comments