1use crate::{
2 flex::{DrawLayout, MeasureLayout},
3 utils::max_optional_size,
4 *,
5};
6
7use super::none::NoneElement;
8
9pub struct Row<F: Fn(&mut RowContent)> {
36 pub gap: f32,
38 pub expand: bool,
40 pub collapse: bool,
42 pub content: F,
53}
54
55impl<F: Fn(&mut RowContent)> Row<F> {
56 pub fn new(content: F) -> Self {
57 Row {
58 gap: 0.,
59 expand: false,
60 collapse: true,
61 content,
62 }
63 }
64
65 pub fn with_gap(self, gap: f32) -> Self {
66 Row { gap, ..self }
67 }
68
69 pub fn with_expand(self, expand: bool) -> Self {
70 Row { expand, ..self }
71 }
72
73 pub fn with_collapse(self, collapse: bool) -> Self {
74 Row { collapse, ..self }
75 }
76
77 pub fn expand(self) -> Self {
78 Row {
79 expand: true,
80 ..self
81 }
82 }
83}
84
85impl<F: Fn(&mut RowContent)> Element for Row<F> {
86 fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
87 FirstLocationUsage::WillUse
88 }
89
90 fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
91 let mut measure_layout = MeasureLayout::new(ctx.width.max, self.gap);
92
93 let mut max_height = None;
94
95 (self.content)(&mut RowContent {
96 text_pieces_cache: ctx.text_pieces_cache,
97 width: ctx.width,
98 first_height: ctx.first_height,
99 pass: Pass::MeasureNonExpanded {
100 layout: &mut measure_layout,
101 max_height: Some(&mut max_height),
102 breakable: ctx.breakable.as_mut(),
103 },
104 });
105
106 let mut width = measure_layout.no_expand_width();
107
108 let draw_layout = measure_layout.build();
109
110 (self.content)(&mut RowContent {
111 text_pieces_cache: ctx.text_pieces_cache,
112 width: ctx.width,
113 first_height: ctx.first_height,
114 pass: Pass::MeasureExpanded {
115 layout: &draw_layout,
116 max_height: &mut max_height,
117 width: if ctx.width.expand {
118 None
119 } else {
120 Some(&mut width)
121 },
122 width_expand: ctx.width.expand,
123 gap: self.gap,
124 breakable: ctx.breakable.as_mut(),
125 },
126 });
127
128 if !self.collapse {
129 if width.is_none() {
130 width = Some(0.);
131 }
132
133 if max_height.is_none() {
134 max_height = Some(0.);
135 }
136 }
137
138 ElementSize {
139 width: if ctx.width.expand {
140 Some(width.map(|w| w.max(ctx.width.max)).unwrap_or(ctx.width.max))
141 } else {
142 width
143 },
144 height: max_height,
145 }
146 }
147
148 fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
149 let mut measure_layout = MeasureLayout::new(ctx.width.max, self.gap);
150
151 let mut max_height = None;
152
153 let mut break_count = 0;
154 let mut extra_location_min_height = None;
155
156 (self.content)(&mut RowContent {
157 text_pieces_cache: ctx.text_pieces_cache,
158 width: ctx.width,
159 first_height: ctx.first_height,
160 pass: Pass::MeasureNonExpanded {
161 layout: &mut measure_layout,
162 max_height: if self.expand {
163 Some(&mut max_height)
164 } else {
165 None
166 },
167 breakable: ctx
168 .breakable
169 .as_ref()
170 .map(|b| BreakableMeasure {
171 full_height: b.full_height,
172
173 break_count: &mut break_count,
175 extra_location_min_height: &mut extra_location_min_height,
176 })
177 .as_mut(),
178 },
179 });
180
181 let draw_layout = measure_layout.build();
182
183 if self.expand {
187 (self.content)(&mut RowContent {
188 text_pieces_cache: ctx.text_pieces_cache,
189 width: ctx.width,
190 first_height: ctx.first_height,
191 pass: Pass::MeasureExpanded {
192 layout: &draw_layout,
193 max_height: &mut max_height,
194 width_expand: ctx.width.expand,
195 width: None, gap: self.gap,
197 breakable: ctx
198 .breakable
199 .as_ref()
200 .map(|b| BreakableMeasure {
201 full_height: b.full_height,
202
203 break_count: &mut break_count,
205 extra_location_min_height: &mut extra_location_min_height,
206 })
207 .as_mut(),
208 },
209 });
210
211 if let Some(ref mut b) = ctx.breakable {
212 match break_count.cmp(&b.preferred_height_break_count) {
213 std::cmp::Ordering::Less => (),
214 std::cmp::Ordering::Equal => {
215 ctx.preferred_height = max_optional_size(ctx.preferred_height, max_height);
216 }
217 std::cmp::Ordering::Greater => {
218 b.preferred_height_break_count = break_count;
219 ctx.preferred_height = max_height;
220 }
221 }
222 } else {
223 ctx.preferred_height = max_optional_size(ctx.preferred_height, max_height);
224 }
225 }
226
227 let mut width = None;
228
229 (self.content)(&mut RowContent {
230 text_pieces_cache: ctx.text_pieces_cache,
231 width: ctx.width,
232 first_height: ctx.first_height,
233 pass: Pass::Draw {
234 layout: &draw_layout,
235 max_height: &mut max_height,
236 width: &mut width,
237 width_expand: ctx.width.expand,
238 gap: self.gap,
239 pdf: ctx.pdf,
240 location: ctx.location,
241 preferred_height: ctx.preferred_height,
242 break_count: 0,
243 breakable: ctx.breakable.as_mut(),
244 },
245 });
246
247 if !self.collapse {
248 if width.is_none() {
249 width = Some(0.);
250 }
251
252 if max_height.is_none() {
253 max_height = Some(0.);
254 }
255 }
256
257 ElementSize {
258 width: if ctx.width.expand {
259 Some(width.map(|w| w.max(ctx.width.max)).unwrap_or(ctx.width.max))
260 } else {
261 width
262 },
263 height: max_height,
264 }
265 }
266}
267
268pub struct RowContent<'a, 'b, 'c> {
269 text_pieces_cache: &'a TextPiecesCache,
270 width: WidthConstraint,
271 first_height: f32,
272 pass: Pass<'a, 'b, 'c>,
273}
274
275enum Pass<'a, 'b, 'c> {
276 MeasureNonExpanded {
277 layout: &'a mut MeasureLayout,
278 max_height: Option<&'a mut Option<f32>>,
279 breakable: Option<&'a mut BreakableMeasure<'b>>,
280 },
281
282 FirstLocationUsage {},
283
284 MeasureExpanded {
285 layout: &'a DrawLayout,
286 max_height: &'a mut Option<f32>,
287 width: Option<&'a mut Option<f32>>,
288 width_expand: bool,
289 gap: f32,
290 breakable: Option<&'a mut BreakableMeasure<'b>>,
291 },
292
293 Draw {
294 layout: &'a DrawLayout,
295 max_height: &'a mut Option<f32>,
296 width: &'a mut Option<f32>,
297 width_expand: bool,
298
299 gap: f32,
300
301 pdf: &'c mut Pdf,
302 location: Location,
303
304 preferred_height: Option<f32>,
305 break_count: u32,
306 breakable: Option<&'a mut BreakableDraw<'b>>,
307 },
308}
309
310#[derive(Copy, Clone, Serialize, Deserialize)]
312pub enum Flex {
313 Expand(u8),
317 SelfSized,
319 Fixed(f32),
321}
322
323fn add_height(
324 max_height: &mut Option<f32>,
325 breakable: Option<&mut BreakableMeasure>,
326 size: ElementSize,
327 break_count: u32,
328 extra_location_min_height: Option<f32>,
329) {
330 if let Some(b) = breakable {
331 *b.extra_location_min_height =
332 max_optional_size(extra_location_min_height, *b.extra_location_min_height);
333
334 match break_count.cmp(b.break_count) {
335 std::cmp::Ordering::Less => (),
336 std::cmp::Ordering::Equal => {
337 *max_height = max_optional_size(*max_height, size.height);
338 }
339 std::cmp::Ordering::Greater => {
340 *b.break_count = break_count;
341 *max_height = size.height;
342 }
343 }
344 } else {
345 *max_height = max_optional_size(*max_height, size.height);
346 }
347}
348
349impl<'a, 'b, 'c> RowContent<'a, 'b, 'c> {
350 pub fn flex_gap(&mut self, gap: u8) {
355 self.add(&NoneElement, Flex::Expand(gap));
356 }
357
358 pub fn add<E: Element>(&mut self, element: &E, flex: Flex) {
359 match self.pass {
360 Pass::MeasureNonExpanded {
361 layout: &mut ref mut layout,
362 ref mut max_height,
363 ref mut breakable,
364 } => match flex {
365 Flex::Expand(fraction) => {
366 layout.add_expand(fraction);
367 }
368 Flex::SelfSized => {
369 let mut break_count = 0;
370 let mut extra_location_min_height = None;
371
372 let size = element.measure(MeasureCtx {
373 text_pieces_cache: self.text_pieces_cache,
374 width: WidthConstraint {
375 expand: false,
376 ..self.width
377 },
378 first_height: self.first_height,
379 breakable: breakable.as_deref_mut().map(|b| BreakableMeasure {
380 full_height: b.full_height,
381 break_count: &mut break_count,
382 extra_location_min_height: &mut extra_location_min_height,
383 }),
384 });
385
386 if let Some(max_height) = max_height {
387 add_height(
389 max_height,
390 breakable.as_deref_mut(),
391 size,
392 break_count,
393 extra_location_min_height,
394 );
395 }
396
397 if let Some(w) = size.width {
399 layout.add_fixed(w);
400 }
401 }
402 Flex::Fixed(width) => {
403 layout.add_fixed(width);
404
405 if let Some(max_height) = max_height {
406 let mut break_count = 0;
407 let mut extra_location_min_height = None;
408
409 let size = element.measure(MeasureCtx {
410 text_pieces_cache: self.text_pieces_cache,
411 width: WidthConstraint {
412 max: width,
413 expand: true,
414 },
415 first_height: self.first_height,
416 breakable: breakable.as_mut().map(|b| BreakableMeasure {
417 full_height: b.full_height,
418 break_count: &mut break_count,
419 extra_location_min_height: &mut extra_location_min_height,
420 }),
421 });
422
423 add_height(
424 max_height,
425 breakable.as_deref_mut(),
426 size,
427 break_count,
428 extra_location_min_height,
429 );
430 }
431 }
432 },
433
434 Pass::MeasureExpanded {
435 layout,
436 max_height: &mut ref mut max_height,
437 ref mut width,
438 width_expand,
439 gap,
440 ref mut breakable,
441 } => match flex {
442 Flex::Expand(fraction) => {
443 let element_width = layout.expand_width(fraction);
444
445 let mut break_count = 0;
446 let mut extra_location_min_height = None;
447
448 let size = element.measure(MeasureCtx {
449 text_pieces_cache: self.text_pieces_cache,
450 width: WidthConstraint {
451 max: element_width,
452 expand: width_expand,
453 },
454 first_height: self.first_height,
455 breakable: breakable.as_deref_mut().map(|b| BreakableMeasure {
456 full_height: b.full_height,
457 break_count: &mut break_count,
458 extra_location_min_height: &mut extra_location_min_height,
459 }),
460 });
461
462 add_height(
463 max_height,
464 breakable.as_deref_mut(),
465 size,
466 break_count,
467 extra_location_min_height,
468 );
469
470 if let &mut Some(&mut ref mut width) = width {
471 if let Some(w) = size.width {
472 if let Some(width) = width {
473 *width += gap + w;
474 } else {
475 *width = Some(w);
476 }
477 }
478 }
479 }
480 Flex::SelfSized => (),
481 Flex::Fixed(_) => (),
482 },
483
484 Pass::Draw {
485 layout,
486 max_height: &mut ref mut max_height,
487 width: &mut ref mut width,
488 width_expand,
489 gap,
490 pdf: &mut ref mut pdf,
491 ref location,
492 preferred_height,
493 ref mut break_count,
494 ref mut breakable,
495 } => {
496 let width_constraint = match flex {
497 Flex::Expand(fraction) => WidthConstraint {
498 max: layout.expand_width(fraction),
499 expand: width_expand,
500 },
501 Flex::SelfSized => WidthConstraint {
502 max: self.width.max,
503 expand: false,
504 },
505 Flex::Fixed(width) => WidthConstraint {
506 max: width,
507 expand: true,
508 },
509 };
510
511 let mut element_break_count = 0;
512
513 let x_offset = if let &mut Some(width) = width {
514 width + gap
515 } else {
516 0.
517 };
518
519 let size = element.draw(DrawCtx {
520 pdf,
521 text_pieces_cache: self.text_pieces_cache,
522 location: Location {
523 pos: (location.pos.0 + x_offset, location.pos.1),
524 ..location.clone()
525 },
526
527 width: width_constraint,
528 first_height: self.first_height,
529 preferred_height,
530
531 breakable: breakable
533 .as_deref_mut()
534 .map(|b| {
535 (
536 b.full_height,
537 b.preferred_height_break_count,
538 |pdf: &mut Pdf, location_idx: u32, _| {
539 element_break_count = element_break_count.max(location_idx + 1);
540
541 let mut new_location = (b.do_break)(
542 pdf,
543 location_idx,
544 Some(if location_idx == 0 {
545 self.first_height
546 } else {
547 b.full_height
548 }),
549 );
550 new_location.pos.0 += x_offset;
551 new_location
552 },
553 )
554 })
555 .as_mut()
556 .map(
557 |&mut (
558 full_height,
559 preferred_height_break_count,
560 ref mut get_location,
561 )| {
562 BreakableDraw {
563 full_height,
564 preferred_height_break_count,
565 do_break: get_location,
566 }
567 },
568 ),
569 });
570
571 if breakable.is_some() {
572 match element_break_count.cmp(break_count) {
573 std::cmp::Ordering::Less => (),
574 std::cmp::Ordering::Equal => {
575 *max_height = max_optional_size(*max_height, size.height);
576 }
577 std::cmp::Ordering::Greater => {
578 *break_count = element_break_count;
579 *max_height = size.height;
580 }
581 }
582 } else {
583 *max_height = max_optional_size(*max_height, size.height);
584 }
585
586 let mut width_add = |w| {
587 if let Some(width) = width {
588 *width += gap + w;
589 } else {
590 *width = Some(w);
591 }
592 };
593
594 match (flex, width_expand) {
595 (Flex::Expand(_), true) | (Flex::Fixed(_), _) => {
596 width_add(width_constraint.max);
597 }
598 (Flex::Expand(_), false) | (Flex::SelfSized, _) => {
599 if let Some(w) = size.width {
600 width_add(w);
601 }
602 }
603 }
604 }
605
606 _ => todo!(),
607 }
608 }
609}
610
611#[cfg(test)]
612mod tests {
613 use super::*;
614 use crate::{
615 elements::{force_break::ForceBreak, none::NoneElement, rectangle::Rectangle},
616 test_utils::{build_element::BuildElementCtx, *},
617 };
618
619 #[test]
620 fn test_empty_row() {
621 let element = Row {
622 gap: 12.,
623 expand: true,
624 collapse: true,
625 content: |_content| {},
626 };
627
628 for output in ElementTestParams::default().run(&element) {
629 output.assert_size(ElementSize {
630 width: if output.width.expand {
631 Some(output.width.max)
632 } else {
633 None
634 },
635 height: None,
636 });
637
638 if let Some(b) = output.breakable {
639 b.assert_break_count(0);
640 b.assert_extra_location_min_height(None);
641 }
642 }
643
644 let element = Row {
645 gap: 12.,
646 expand: false,
647 collapse: true,
648 content: |_content| {},
649 };
650
651 for output in ElementTestParams::default().run(&element) {
652 output.assert_size(ElementSize {
653 width: if output.width.expand {
654 Some(output.width.max)
655 } else {
656 None
657 },
658 height: None,
659 });
660
661 if let Some(b) = output.breakable {
662 b.assert_break_count(0);
663 b.assert_extra_location_min_height(None);
664 }
665 }
666 }
667
668 #[test]
669 fn test_row_expand() {
670 test_row(true);
671 }
672
673 #[test]
674 fn test_row_no_expand() {
675 test_row(false);
676 }
677
678 fn test_row(expand: bool) {
679 use assert_passes::*;
680
681 let gap = 1.;
682
683 let element = BuildElement(
684 |BuildElementCtx {
685 width,
686 first_height,
687 mut pass,
688 },
689 callback| {
690 let less_first_height = first_height == 6.;
691
692 if let build_element::Pass::Draw {
695 preferred_height,
696 breakable,
697 } = &mut pass
698 {
699 if preferred_height.is_some() || expand {
700 if let Some(breakable) = breakable {
701 let (height, break_count) =
702 if less_first_height { (6., 2) } else { (12., 1) };
703
704 *preferred_height = Some(height);
705 breakable.preferred_height_break_count = break_count;
706 } else {
707 *preferred_height = Some(24.);
708 }
709 }
710 }
711
712 let child_0 = AssertPasses::new(
713 NoneElement,
714 match pass {
715 build_element::Pass::FirstLocationUsage { .. } => todo!(),
716 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
717 width: WidthConstraint {
718 max: width.max,
719 expand: false,
720 },
721 first_height,
722 full_height,
723 }],
724 build_element::Pass::Draw {
725 ref breakable,
726 preferred_height,
727 } => vec![
728 Pass::Measure {
729 width: WidthConstraint {
730 max: width.max,
731 expand: false,
732 },
733 first_height: first_height,
734 full_height: breakable.as_ref().map(|b| b.full_height),
735 },
736 Pass::Draw {
737 width: WidthConstraint {
738 max: width.max,
739 expand: false,
740 },
741 first_height: first_height,
742 preferred_height,
743
744 page: 0,
745 layer: 0,
746 pos: (12., 14.),
747
748 breakable: breakable.as_ref().map(|b| BreakableDraw {
749 full_height: b.full_height,
750 preferred_height_break_count: b.preferred_height_break_count,
751 breaks: Vec::new(),
752 }),
753 },
754 ],
755 },
756 );
757
758 let child_1 = {
759 let width = WidthConstraint {
760 max: 6.,
761 expand: width.expand,
762 };
763
764 AssertPasses::new(
765 Rectangle {
766 size: (5., 5.),
767 fill: None,
768 outline: None,
769 },
770 match pass {
771 build_element::Pass::FirstLocationUsage { .. } => todo!(),
772 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
773 width,
774 first_height,
775 full_height,
776 }],
777 build_element::Pass::Draw {
778 ref breakable,
779 preferred_height,
780 } => {
781 let mut r = Vec::new();
782
783 if expand {
784 r.push(Pass::Measure {
785 width,
786 first_height,
787 full_height: breakable.as_ref().map(|b| b.full_height),
788 });
789 }
790
791 r.push(Pass::Draw {
792 width,
793 first_height,
794 preferred_height,
795
796 page: 0,
797 layer: 0,
798 pos: (12., 14.),
799
800 breakable: breakable.as_ref().map(|b| BreakableDraw {
801 full_height: b.full_height,
802 preferred_height_break_count: b
803 .preferred_height_break_count,
804 breaks: Vec::new(),
805 }),
806 });
807
808 r
809 }
810 },
811 )
812 };
813
814 let child_1_width = if width.expand { 6. } else { 5. } + gap;
815
816 let child_2 = {
817 let x = 12. + child_1_width;
818
819 AssertPasses::new(
820 FakeText {
821 lines: 12,
822 line_height: 2.,
823 width: 500.,
824 },
825 match pass {
826 build_element::Pass::FirstLocationUsage { .. } => todo!(),
827 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
828 width: WidthConstraint {
829 max: 3.,
830 expand: true,
831 },
832 first_height,
833 full_height,
834 }],
835 build_element::Pass::Draw {
836 ref breakable,
837 preferred_height,
838 } => {
839 let mut r = Vec::new();
840
841 if expand {
842 r.push(Pass::Measure {
843 width: WidthConstraint {
844 max: 3.,
845 expand: true,
846 },
847 first_height,
848 full_height: breakable.as_ref().map(|b| b.full_height),
849 });
850 }
851
852 r.push(Pass::Draw {
853 width: WidthConstraint {
854 max: 3.,
855 expand: true,
856 },
857 first_height,
858 preferred_height,
859
860 page: 0,
861 layer: 0,
862 pos: (x, 14.),
863
864 breakable: breakable.as_ref().map(|b| BreakableDraw {
865 full_height: b.full_height,
866 preferred_height_break_count: b
867 .preferred_height_break_count,
868 breaks: if less_first_height {
869 vec![
870 Break {
871 page: 1,
872 layer: 0,
873 pos: (x, 14.),
874 },
875 Break {
876 page: 2,
877 layer: 0,
878 pos: (x, 14.),
879 },
880 ]
881 } else {
882 vec![Break {
883 page: 1,
884 layer: 0,
885 pos: (x, 14.),
886 }]
887 },
888 }),
889 });
890
891 r
892 }
893 },
894 )
895 };
896
897 let child_2_width = 3. + gap;
898
899 let child_3 = {
900 let x = 12. + child_1_width + child_2_width;
901 let width = WidthConstraint {
902 max: 13.,
903 expand: width.expand,
904 };
905
906 AssertPasses::new(
907 ForceBreak,
908 match pass {
909 build_element::Pass::FirstLocationUsage { .. } => todo!(),
910 build_element::Pass::Measure { full_height } => vec![Pass::Measure {
911 width,
912 first_height,
913 full_height,
914 }],
915 build_element::Pass::Draw {
916 ref breakable,
917 preferred_height,
918 } => {
919 let mut r = Vec::new();
920
921 if expand {
922 r.push(Pass::Measure {
923 width,
924 first_height,
925 full_height: breakable.as_ref().map(|b| b.full_height),
926 });
927 }
928
929 r.push(Pass::Draw {
930 width,
931 first_height,
932 preferred_height,
933
934 page: 0,
935 layer: 0,
936 pos: (x, 14.),
937
938 breakable: breakable.as_ref().map(|b| BreakableDraw {
939 full_height: b.full_height,
940 preferred_height_break_count: b
941 .preferred_height_break_count,
942 breaks: vec![Break {
943 page: 1,
944 layer: 0,
945 pos: (x, 14.),
946 }],
947 }),
948 });
949
950 r
951 }
952 },
953 )
954 };
955
956 let element = Row {
957 gap,
958 expand,
959 collapse: false,
960 content: |content| {
961 content.add(&child_0, Flex::SelfSized);
962 content.add(&child_1, Flex::Expand(1));
963 content.add(&child_2, Flex::Fixed(3.));
964 content.add(&child_3, Flex::Expand(2));
965 },
966 };
967
968 callback.call(element)
969 },
970 );
971
972 for output in (ElementTestParams {
973 width: 24.,
974 first_height: 6.,
975 full_height: 12.,
976 pos: (12., 14.),
977 ..Default::default()
978 })
979 .run(&element)
980 {
981 let (height, breaks) = if output.breakable.is_none() {
982 (24., 0)
983 } else if output.first_height == 6. {
984 (6., 2)
985 } else {
986 (12., 1)
987 };
988
989 output.assert_size(ElementSize {
990 width: if output.width.expand {
991 Some(20. + gap + 3.)
992 } else {
993 Some(5. + gap + 3.)
994 },
995 height: Some(height),
996 });
997
998 if let Some(b) = output.breakable {
999 b.assert_break_count(breaks);
1000
1001 b.assert_extra_location_min_height(None);
1003 }
1004 }
1005 }
1006}