1use taffy::prelude as tf;
10
11use super::style::{
12 AlignContent, AlignItems, AlignSelf, BoxSizing, CssStyle, Display, Edges, FlexDirection,
13 FlexWrap, Gap, GridAutoFlow, GridLine, GridLineEnd, GridTrack, GridTrackKeyword,
14 JustifyContent, JustifyItems, JustifySelf, Overflow, Position, Size,
15};
16use super::units::{LengthContext, LengthPercentage, ParsedLength};
17
18#[derive(Debug, Clone, Copy, Default)]
19pub struct ConversionContext {
20 pub length: LengthContext,
21}
22
23pub fn to_taffy_style(css: &CssStyle, ctx: &ConversionContext) -> tf::Style {
27 let mut style = tf::Style::DEFAULT;
28
29 style.display = match css.display {
31 Some(Display::None) => tf::Display::None,
32 Some(Display::Block) => tf::Display::Block,
33 Some(Display::Flex) => tf::Display::Flex,
34 Some(Display::Grid) => tf::Display::Grid,
35 _ => tf::Display::Block,
37 };
38
39 style.position = match css.position {
41 Some(Position::Absolute) => tf::Position::Absolute,
42 _ => tf::Position::Relative,
43 };
44
45 style.inset = tf::Rect {
47 top: lp_to_lp_auto(css.top.as_ref(), ctx),
48 right: lp_to_lp_auto(css.right.as_ref(), ctx),
49 bottom: lp_to_lp_auto(css.bottom.as_ref(), ctx),
50 left: lp_to_lp_auto(css.left.as_ref(), ctx),
51 };
52
53 style.size = tf::Size {
55 width: size_to_dim(css.width.as_ref(), ctx),
56 height: size_to_dim(css.height.as_ref(), ctx),
57 };
58 style.min_size = tf::Size {
59 width: size_to_dim(css.min_width.as_ref(), ctx),
60 height: size_to_dim(css.min_height.as_ref(), ctx),
61 };
62 style.max_size = tf::Size {
63 width: size_to_dim(css.max_width.as_ref(), ctx),
64 height: size_to_dim(css.max_height.as_ref(), ctx),
65 };
66 style.aspect_ratio = css.aspect_ratio;
67
68 if let Some(bs) = css.box_sizing {
74 style.box_sizing = match bs {
75 BoxSizing::ContentBox => tf::BoxSizing::ContentBox,
76 BoxSizing::BorderBox => tf::BoxSizing::BorderBox,
77 };
78 }
79
80 style.margin = edges_to_rect_lpa(css.margin.as_ref(), ctx);
82 style.padding = edges_to_rect_lp(css.padding.as_ref(), ctx);
83 style.border = border_widths(css.border.as_ref(), ctx);
84
85 if let Some(d) = css.flex_direction {
87 style.flex_direction = match d {
88 FlexDirection::Row => tf::FlexDirection::Row,
89 FlexDirection::RowReverse => tf::FlexDirection::RowReverse,
90 FlexDirection::Column => tf::FlexDirection::Column,
91 FlexDirection::ColumnReverse => tf::FlexDirection::ColumnReverse,
92 };
93 }
94 if let Some(w) = css.flex_wrap {
95 style.flex_wrap = match w {
96 FlexWrap::Nowrap => tf::FlexWrap::NoWrap,
97 FlexWrap::Wrap => tf::FlexWrap::Wrap,
98 FlexWrap::WrapReverse => tf::FlexWrap::WrapReverse,
99 };
100 }
101 if let Some(j) = css.justify_content {
102 style.justify_content = Some(match j {
103 JustifyContent::FlexStart | JustifyContent::Start => tf::JustifyContent::Start,
104 JustifyContent::FlexEnd | JustifyContent::End => tf::JustifyContent::End,
105 JustifyContent::Center => tf::JustifyContent::Center,
106 JustifyContent::SpaceBetween => tf::JustifyContent::SpaceBetween,
107 JustifyContent::SpaceAround => tf::JustifyContent::SpaceAround,
108 JustifyContent::SpaceEvenly => tf::JustifyContent::SpaceEvenly,
109 });
110 }
111 if let Some(a) = css.align_items {
112 style.align_items = Some(align_items_to_taffy(a));
113 }
114 if let Some(a) = css.align_self {
115 style.align_self = align_self_to_taffy(a);
116 }
117 if let Some(a) = css.align_content {
118 style.align_content = Some(align_content_to_taffy(a));
119 }
120 if let Some(grow) = css.flex_grow {
121 style.flex_grow = grow;
122 }
123 if let Some(shrink) = css.flex_shrink {
124 style.flex_shrink = shrink;
125 }
126 if let Some(basis) = css.flex_basis.as_ref() {
127 style.flex_basis = size_to_dim(Some(basis), ctx);
128 }
129 if css.order.is_some() {
143 static WARNED_ORDER: std::sync::Once = std::sync::Once::new();
144 WARNED_ORDER.call_once(|| {
145 eprintln!(
146 "Warning: `order` is not supported by the layout engine (no flex/grid item \
147 reordering primitive) — it is ignored. Reorder the component's JSON `children` \
148 array instead to change paint/layout order."
149 );
150 });
151 }
152
153 if let Some(gap) = css.gap.as_ref() {
155 let (row, col) = match gap {
156 Gap::Uniform(v) => (lp_to_lp(v, ctx), lp_to_lp(v, ctx)),
157 Gap::RowColumn { row, column } => (lp_to_lp(row, ctx), lp_to_lp(column, ctx)),
158 };
159 style.gap = tf::Size {
160 width: col,
161 height: row,
162 };
163 }
164
165 if let Some(tracks) = css.grid_template_columns.as_ref() {
167 style.grid_template_columns = tracks
168 .iter()
169 .map(|t| tf::GridTemplateComponent::Single(grid_track_sizing(t, ctx)))
170 .collect();
171 }
172 if let Some(tracks) = css.grid_template_rows.as_ref() {
173 style.grid_template_rows = tracks
174 .iter()
175 .map(|t| tf::GridTemplateComponent::Single(grid_track_sizing(t, ctx)))
176 .collect();
177 }
178 if let Some(flow) = css.grid_auto_flow {
179 style.grid_auto_flow = match flow {
180 GridAutoFlow::Row => tf::GridAutoFlow::Row,
181 GridAutoFlow::Column => tf::GridAutoFlow::Column,
182 GridAutoFlow::RowDense => tf::GridAutoFlow::RowDense,
183 GridAutoFlow::ColumnDense => tf::GridAutoFlow::ColumnDense,
184 };
185 }
186 if let Some(gc) = css.grid_column.as_ref() {
187 style.grid_column = grid_placement_line(gc);
188 }
189 if let Some(gr) = css.grid_row.as_ref() {
190 style.grid_row = grid_placement_line(gr);
191 }
192 if let Some(ji) = css.justify_items {
198 style.justify_items = Some(justify_items_to_taffy(ji));
199 }
200 if let Some(js) = css.justify_self {
201 style.justify_self = justify_self_to_taffy(js);
202 }
203
204 if let Some(o) = css.overflow {
206 let v = overflow_to_taffy(o);
207 style.overflow = taffy::Point { x: v, y: v };
208 }
209 if let Some(o) = css.overflow_x {
210 style.overflow.x = overflow_to_taffy(o);
211 }
212 if let Some(o) = css.overflow_y {
213 style.overflow.y = overflow_to_taffy(o);
214 }
215
216 style
217}
218
219fn align_items_to_taffy(a: AlignItems) -> tf::AlignItems {
220 match a {
221 AlignItems::Stretch => tf::AlignItems::Stretch,
222 AlignItems::FlexStart | AlignItems::Start => tf::AlignItems::Start,
223 AlignItems::FlexEnd | AlignItems::End => tf::AlignItems::End,
224 AlignItems::Center => tf::AlignItems::Center,
225 AlignItems::Baseline => tf::AlignItems::Baseline,
226 }
227}
228
229fn align_self_to_taffy(a: AlignSelf) -> Option<tf::AlignSelf> {
230 Some(match a {
231 AlignSelf::Auto => return None,
232 AlignSelf::Stretch => tf::AlignSelf::Stretch,
233 AlignSelf::FlexStart | AlignSelf::Start => tf::AlignSelf::Start,
234 AlignSelf::FlexEnd | AlignSelf::End => tf::AlignSelf::End,
235 AlignSelf::Center => tf::AlignSelf::Center,
236 AlignSelf::Baseline => tf::AlignSelf::Baseline,
237 })
238}
239
240fn justify_items_to_taffy(j: JustifyItems) -> tf::AlignItems {
241 match j {
242 JustifyItems::Stretch => tf::AlignItems::Stretch,
243 JustifyItems::Start => tf::AlignItems::Start,
244 JustifyItems::End => tf::AlignItems::End,
245 JustifyItems::Center => tf::AlignItems::Center,
246 JustifyItems::Legacy => tf::AlignItems::Start,
251 }
252}
253
254fn justify_self_to_taffy(j: JustifySelf) -> Option<tf::AlignSelf> {
255 Some(match j {
256 JustifySelf::Auto => return None,
260 JustifySelf::Stretch => tf::AlignSelf::Stretch,
261 JustifySelf::Start => tf::AlignSelf::Start,
262 JustifySelf::End => tf::AlignSelf::End,
263 JustifySelf::Center => tf::AlignSelf::Center,
264 })
265}
266
267fn align_content_to_taffy(a: AlignContent) -> tf::AlignContent {
268 match a {
269 AlignContent::Stretch => tf::AlignContent::Stretch,
270 AlignContent::FlexStart | AlignContent::Start => tf::AlignContent::Start,
271 AlignContent::FlexEnd | AlignContent::End => tf::AlignContent::End,
272 AlignContent::Center => tf::AlignContent::Center,
273 AlignContent::SpaceBetween => tf::AlignContent::SpaceBetween,
274 AlignContent::SpaceAround => tf::AlignContent::SpaceAround,
275 AlignContent::SpaceEvenly => tf::AlignContent::SpaceEvenly,
276 }
277}
278
279fn overflow_to_taffy(o: Overflow) -> taffy::Overflow {
280 match o {
281 Overflow::Visible => taffy::Overflow::Visible,
282 Overflow::Hidden | Overflow::Clip => taffy::Overflow::Hidden,
283 Overflow::Auto | Overflow::Scroll => taffy::Overflow::Scroll,
284 }
285}
286
287fn lp_to_lp(v: &LengthPercentage, ctx: &ConversionContext) -> tf::LengthPercentage {
289 match v.parse() {
290 ParsedLength::Px(p) => tf::LengthPercentage::length(p),
291 ParsedLength::Percent(p) => tf::LengthPercentage::percent(p / 100.0),
292 ParsedLength::Em(em) => tf::LengthPercentage::length(em * ctx.length.font_size),
293 ParsedLength::Rem(r) => tf::LengthPercentage::length(r * ctx.length.root_font_size),
294 ParsedLength::Vw(p) => tf::LengthPercentage::length(p / 100.0 * ctx.length.viewport_width),
295 ParsedLength::Vh(p) => tf::LengthPercentage::length(p / 100.0 * ctx.length.viewport_height),
296 ParsedLength::Fr(_) | ParsedLength::Auto => tf::LengthPercentage::length(0.0),
297 }
298}
299
300fn lp_to_lp_auto(
302 v: Option<&LengthPercentage>,
303 ctx: &ConversionContext,
304) -> tf::LengthPercentageAuto {
305 let Some(v) = v else {
306 return tf::LengthPercentageAuto::auto();
307 };
308 match v.parse() {
309 ParsedLength::Auto => tf::LengthPercentageAuto::auto(),
310 ParsedLength::Px(p) => tf::LengthPercentageAuto::length(p),
311 ParsedLength::Percent(p) => tf::LengthPercentageAuto::percent(p / 100.0),
312 ParsedLength::Em(em) => tf::LengthPercentageAuto::length(em * ctx.length.font_size),
313 ParsedLength::Rem(r) => tf::LengthPercentageAuto::length(r * ctx.length.root_font_size),
314 ParsedLength::Vw(p) => {
315 tf::LengthPercentageAuto::length(p / 100.0 * ctx.length.viewport_width)
316 }
317 ParsedLength::Vh(p) => {
318 tf::LengthPercentageAuto::length(p / 100.0 * ctx.length.viewport_height)
319 }
320 ParsedLength::Fr(_) => tf::LengthPercentageAuto::auto(),
321 }
322}
323
324fn size_to_dim(s: Option<&Size>, ctx: &ConversionContext) -> tf::Dimension {
326 let Some(s) = s else {
327 return tf::Dimension::auto();
328 };
329 match s {
330 Size::Auto(_) => tf::Dimension::auto(),
331 Size::Length(lp) => match lp.parse() {
332 ParsedLength::Auto => tf::Dimension::auto(),
333 ParsedLength::Px(p) => tf::Dimension::length(p),
334 ParsedLength::Percent(p) => tf::Dimension::percent(p / 100.0),
335 ParsedLength::Em(em) => tf::Dimension::length(em * ctx.length.font_size),
336 ParsedLength::Rem(r) => tf::Dimension::length(r * ctx.length.root_font_size),
337 ParsedLength::Vw(p) => tf::Dimension::length(p / 100.0 * ctx.length.viewport_width),
338 ParsedLength::Vh(p) => tf::Dimension::length(p / 100.0 * ctx.length.viewport_height),
339 ParsedLength::Fr(_) => tf::Dimension::auto(),
340 },
341 Size::Keyword(_) => {
342 tf::Dimension::auto()
345 }
346 }
347}
348
349fn edges_to_rect_lp(e: Option<&Edges>, ctx: &ConversionContext) -> tf::Rect<tf::LengthPercentage> {
350 let Some(e) = e else {
351 return tf::Rect {
352 top: tf::LengthPercentage::length(0.0),
353 right: tf::LengthPercentage::length(0.0),
354 bottom: tf::LengthPercentage::length(0.0),
355 left: tf::LengthPercentage::length(0.0),
356 };
357 };
358 let (top, right, bottom, left) = e.resolve();
359 tf::Rect {
360 top: lp_to_lp(&top, ctx),
361 right: lp_to_lp(&right, ctx),
362 bottom: lp_to_lp(&bottom, ctx),
363 left: lp_to_lp(&left, ctx),
364 }
365}
366
367fn edges_to_rect_lpa(
368 e: Option<&Edges>,
369 ctx: &ConversionContext,
370) -> tf::Rect<tf::LengthPercentageAuto> {
371 let Some(e) = e else {
372 return tf::Rect {
373 top: tf::LengthPercentageAuto::length(0.0),
374 right: tf::LengthPercentageAuto::length(0.0),
375 bottom: tf::LengthPercentageAuto::length(0.0),
376 left: tf::LengthPercentageAuto::length(0.0),
377 };
378 };
379 let (top, right, bottom, left) = e.resolve();
380 tf::Rect {
381 top: lp_to_lp_auto(Some(&top), ctx),
382 right: lp_to_lp_auto(Some(&right), ctx),
383 bottom: lp_to_lp_auto(Some(&bottom), ctx),
384 left: lp_to_lp_auto(Some(&left), ctx),
385 }
386}
387
388fn border_widths(
389 b: Option<&super::style::BorderEdges>,
390 ctx: &ConversionContext,
391) -> tf::Rect<tf::LengthPercentage> {
392 let Some(b) = b else {
393 return tf::Rect {
394 top: tf::LengthPercentage::length(0.0),
395 right: tf::LengthPercentage::length(0.0),
396 bottom: tf::LengthPercentage::length(0.0),
397 left: tf::LengthPercentage::length(0.0),
398 };
399 };
400 let uniform = b.width.as_ref().map(|e| e.resolve());
402 let pick_side = |side: Option<&super::style::BorderSide>, idx: usize| -> tf::LengthPercentage {
403 if let Some(side) = side {
404 if let Some(w) = side.width.as_ref() {
405 let lp = LengthPercentage::Px(w.resolve(&ctx.length));
406 return lp_to_lp(&lp, ctx);
407 }
408 }
409 if let Some((t, r, btm, l)) = uniform.as_ref() {
410 let pick = match idx {
411 0 => t,
412 1 => r,
413 2 => btm,
414 3 => l,
415 _ => t,
416 };
417 return lp_to_lp(pick, ctx);
418 }
419 tf::LengthPercentage::length(0.0)
420 };
421 tf::Rect {
422 top: pick_side(b.top.as_ref(), 0),
423 right: pick_side(b.right.as_ref(), 1),
424 bottom: pick_side(b.bottom.as_ref(), 2),
425 left: pick_side(b.left.as_ref(), 3),
426 }
427}
428
429fn grid_track_sizing(t: &GridTrack, ctx: &ConversionContext) -> tf::TrackSizingFunction {
436 match t {
437 GridTrack::Fr(n) => tf::flex(*n),
438 GridTrack::Keyword(k) => grid_keyword_sizing(*k),
439 GridTrack::Length(lp) => grid_length_sizing(lp, ctx),
440 GridTrack::Minmax { min, max } => {
441 tf::minmax(grid_track_min(min, ctx), grid_track_max(max, ctx))
442 }
443 }
444}
445
446fn grid_keyword_sizing(k: GridTrackKeyword) -> tf::TrackSizingFunction {
447 match k {
448 GridTrackKeyword::Auto => tf::auto(),
449 GridTrackKeyword::MinContent => tf::min_content(),
450 GridTrackKeyword::MaxContent => tf::max_content(),
451 }
452}
453
454fn grid_length_sizing(lp: &LengthPercentage, ctx: &ConversionContext) -> tf::TrackSizingFunction {
455 match lp.parse() {
456 ParsedLength::Fr(n) => tf::flex(n),
457 ParsedLength::Auto => tf::auto(),
458 ParsedLength::Px(p) => tf::length(p),
459 ParsedLength::Percent(p) => tf::percent(p / 100.0),
460 ParsedLength::Em(em) => tf::length(em * ctx.length.font_size),
461 ParsedLength::Rem(r) => tf::length(r * ctx.length.root_font_size),
462 ParsedLength::Vw(p) => tf::length(p / 100.0 * ctx.length.viewport_width),
463 ParsedLength::Vh(p) => tf::length(p / 100.0 * ctx.length.viewport_height),
464 }
465}
466
467fn grid_track_min(t: &GridTrack, ctx: &ConversionContext) -> tf::MinTrackSizingFunction {
471 match t {
472 GridTrack::Fr(_) => tf::auto(),
473 GridTrack::Keyword(GridTrackKeyword::Auto) => tf::auto(),
474 GridTrack::Keyword(GridTrackKeyword::MinContent) => tf::min_content(),
475 GridTrack::Keyword(GridTrackKeyword::MaxContent) => tf::max_content(),
476 GridTrack::Length(lp) => grid_length_min(lp, ctx),
477 GridTrack::Minmax { min, .. } => grid_track_min(min, ctx),
480 }
481}
482
483fn grid_length_min(lp: &LengthPercentage, ctx: &ConversionContext) -> tf::MinTrackSizingFunction {
484 match lp.parse() {
485 ParsedLength::Fr(_) | ParsedLength::Auto => tf::auto(),
486 ParsedLength::Px(p) => tf::length(p),
487 ParsedLength::Percent(p) => tf::percent(p / 100.0),
488 ParsedLength::Em(em) => tf::length(em * ctx.length.font_size),
489 ParsedLength::Rem(r) => tf::length(r * ctx.length.root_font_size),
490 ParsedLength::Vw(p) => tf::length(p / 100.0 * ctx.length.viewport_width),
491 ParsedLength::Vh(p) => tf::length(p / 100.0 * ctx.length.viewport_height),
492 }
493}
494
495fn grid_track_max(t: &GridTrack, ctx: &ConversionContext) -> tf::MaxTrackSizingFunction {
497 match t {
498 GridTrack::Fr(n) => tf::fr(*n),
499 GridTrack::Keyword(GridTrackKeyword::Auto) => tf::auto(),
500 GridTrack::Keyword(GridTrackKeyword::MinContent) => tf::min_content(),
501 GridTrack::Keyword(GridTrackKeyword::MaxContent) => tf::max_content(),
502 GridTrack::Length(lp) => grid_length_max(lp, ctx),
503 GridTrack::Minmax { max, .. } => grid_track_max(max, ctx),
504 }
505}
506
507fn grid_length_max(lp: &LengthPercentage, ctx: &ConversionContext) -> tf::MaxTrackSizingFunction {
508 match lp.parse() {
509 ParsedLength::Fr(n) => tf::fr(n),
510 ParsedLength::Auto => tf::auto(),
511 ParsedLength::Px(p) => tf::length(p),
512 ParsedLength::Percent(p) => tf::percent(p / 100.0),
513 ParsedLength::Em(em) => tf::length(em * ctx.length.font_size),
514 ParsedLength::Rem(r) => tf::length(r * ctx.length.root_font_size),
515 ParsedLength::Vw(p) => tf::length(p / 100.0 * ctx.length.viewport_width),
516 ParsedLength::Vh(p) => tf::length(p / 100.0 * ctx.length.viewport_height),
517 }
518}
519
520fn grid_placement_line(g: &GridLine) -> tf::Line<tf::GridPlacement> {
524 let start = g.start.map(|GridLineEnd::Index(i)| i as i16);
525 let end = g.end.map(|GridLineEnd::Index(i)| i as i16);
526 match (start, end, g.span) {
527 (Some(s), Some(e), _) => tf::Line {
528 start: tf::line(s),
529 end: tf::line(e),
530 },
531 (Some(s), None, Some(n)) => tf::Line {
532 start: tf::line(s),
533 end: tf::span(n),
534 },
535 (Some(s), None, None) => tf::Line {
536 start: tf::line(s),
537 end: tf::auto(),
538 },
539 (None, Some(e), Some(n)) => tf::Line {
540 start: tf::span(n),
541 end: tf::line(e),
542 },
543 (None, Some(e), None) => tf::Line {
544 start: tf::auto(),
545 end: tf::line(e),
546 },
547 (None, None, Some(n)) => tf::Line {
548 start: tf::span(n),
549 end: tf::auto(),
550 },
551 (None, None, None) => tf::Line::auto(),
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558 use crate::css::style::*;
559
560 fn ctx() -> ConversionContext {
561 ConversionContext::default()
562 }
563
564 #[test]
565 fn empty_style_yields_taffy_default() {
566 let css = CssStyle::default();
567 let s = to_taffy_style(&css, &ctx());
568 assert_eq!(s.display, tf::Display::Block);
569 assert_eq!(s.flex_grow, 0.0);
570 }
571
572 #[test]
573 fn flex_column_with_gap() {
574 let css = CssStyle {
575 display: Some(Display::Flex),
576 flex_direction: Some(FlexDirection::Column),
577 gap: Some(Gap::Uniform(LengthPercentage::Px(16.0))),
578 align_items: Some(AlignItems::Center),
579 ..Default::default()
580 };
581 let s = to_taffy_style(&css, &ctx());
582 assert_eq!(s.display, tf::Display::Flex);
583 assert_eq!(s.flex_direction, tf::FlexDirection::Column);
584 assert_eq!(s.align_items, Some(tf::AlignItems::Center));
585 assert_eq!(s.gap.height, tf::LengthPercentage::length(16.0));
586 }
587
588 #[test]
589 fn padding_uniform_resolved() {
590 let css = CssStyle {
591 padding: Some(Edges::Uniform(LengthPercentage::Px(24.0))),
592 ..Default::default()
593 };
594 let s = to_taffy_style(&css, &ctx());
595 assert_eq!(s.padding.top, tf::LengthPercentage::length(24.0));
596 assert_eq!(s.padding.left, tf::LengthPercentage::length(24.0));
597 }
598
599 #[test]
600 fn width_percent() {
601 let css = CssStyle {
602 width: Some(Size::Length(LengthPercentage::String("50%".into()))),
603 ..Default::default()
604 };
605 let s = to_taffy_style(&css, &ctx());
606 assert_eq!(s.size.width, tf::Dimension::percent(0.5));
607 }
608
609 #[test]
610 fn position_absolute_inset() {
611 let css = CssStyle {
612 position: Some(Position::Absolute),
613 top: Some(LengthPercentage::Px(10.0)),
614 left: Some(LengthPercentage::Px(20.0)),
615 ..Default::default()
616 };
617 let s = to_taffy_style(&css, &ctx());
618 assert_eq!(s.position, tf::Position::Absolute);
619 assert_eq!(s.inset.top, tf::LengthPercentageAuto::length(10.0));
620 assert_eq!(s.inset.left, tf::LengthPercentageAuto::length(20.0));
621 }
622
623 #[test]
624 fn flex_grow_shrink_basis() {
625 let css = CssStyle {
626 flex_grow: Some(2.0),
627 flex_shrink: Some(0.5),
628 flex_basis: Some(Size::Length(LengthPercentage::Px(100.0))),
629 ..Default::default()
630 };
631 let s = to_taffy_style(&css, &ctx());
632 assert_eq!(s.flex_grow, 2.0);
633 assert_eq!(s.flex_shrink, 0.5);
634 assert_eq!(s.flex_basis, tf::Dimension::length(100.0));
635 }
636
637 #[test]
638 fn overflow_hidden() {
639 let css = CssStyle {
640 overflow: Some(Overflow::Hidden),
641 ..Default::default()
642 };
643 let s = to_taffy_style(&css, &ctx());
644 assert_eq!(s.overflow.x, taffy::Overflow::Hidden);
645 assert_eq!(s.overflow.y, taffy::Overflow::Hidden);
646 }
647
648 #[test]
651 fn grid_display_translated() {
652 let css = CssStyle {
653 display: Some(Display::Grid),
654 ..Default::default()
655 };
656 let s = to_taffy_style(&css, &ctx());
657 assert_eq!(s.display, tf::Display::Grid);
658 }
659
660 #[test]
661 fn grid_template_columns_fr_tracks_translated() {
662 let css = CssStyle {
663 grid_template_columns: Some(vec![GridTrack::Fr(1.0), GridTrack::Fr(2.0)]),
664 ..Default::default()
665 };
666 let s = to_taffy_style(&css, &ctx());
667 assert_eq!(s.grid_template_columns.len(), 2);
668 }
669
670 #[test]
671 fn grid_template_rows_string_fr_tracks_translated() {
672 let css = CssStyle {
674 grid_template_rows: Some(vec![
675 GridTrack::Length(LengthPercentage::String("1fr".into())),
676 GridTrack::Length(LengthPercentage::String("1fr".into())),
677 ]),
678 ..Default::default()
679 };
680 let s = to_taffy_style(&css, &ctx());
681 assert_eq!(s.grid_template_rows.len(), 2);
682 }
683
684 #[test]
685 fn grid_auto_flow_column_translated() {
686 let css = CssStyle {
687 grid_auto_flow: Some(GridAutoFlow::Column),
688 ..Default::default()
689 };
690 let s = to_taffy_style(&css, &ctx());
691 assert_eq!(s.grid_auto_flow, tf::GridAutoFlow::Column);
692 }
693
694 #[test]
695 fn grid_column_start_and_span_translated() {
696 let css = CssStyle {
697 grid_column: Some(GridLine {
698 start: Some(GridLineEnd::Index(2)),
699 span: Some(3),
700 ..Default::default()
701 }),
702 ..Default::default()
703 };
704 let s = to_taffy_style(&css, &ctx());
705 assert!(matches!(s.grid_column.start, tf::GridPlacement::Line(_)));
706 assert!(matches!(s.grid_column.end, tf::GridPlacement::Span(3)));
707 }
708
709 #[test]
710 fn grid_row_start_end_translated() {
711 let css = CssStyle {
712 grid_row: Some(GridLine {
713 start: Some(GridLineEnd::Index(1)),
714 end: Some(GridLineEnd::Index(3)),
715 ..Default::default()
716 }),
717 ..Default::default()
718 };
719 let s = to_taffy_style(&css, &ctx());
720 assert!(matches!(s.grid_row.start, tf::GridPlacement::Line(_)));
721 assert!(matches!(s.grid_row.end, tf::GridPlacement::Line(_)));
722 }
723
724 #[test]
725 fn grid_gap_applies_to_both_axes() {
726 let css = CssStyle {
727 display: Some(Display::Grid),
728 gap: Some(Gap::Uniform(LengthPercentage::Px(24.0))),
729 ..Default::default()
730 };
731 let s = to_taffy_style(&css, &ctx());
732 assert_eq!(s.gap.width, tf::LengthPercentage::length(24.0));
733 assert_eq!(s.gap.height, tf::LengthPercentage::length(24.0));
734 }
735
736 #[test]
743 fn grid_three_fr_columns_produce_distinct_x_offsets() {
744 let root_css = CssStyle {
745 display: Some(Display::Grid),
746 width: Some(Size::Length(LengthPercentage::Px(900.0))),
750 height: Some(Size::Length(LengthPercentage::Px(300.0))),
751 grid_template_columns: Some(vec![
752 GridTrack::Fr(1.0),
753 GridTrack::Fr(1.0),
754 GridTrack::Fr(1.0),
755 ]),
756 ..Default::default()
757 };
758 let root_style = to_taffy_style(&root_css, &ctx());
759 let leaf_style = to_taffy_style(&CssStyle::default(), &ctx());
760
761 let mut tree: tf::TaffyTree = tf::TaffyTree::new();
762 let c1 = tree.new_leaf(leaf_style.clone()).unwrap();
763 let c2 = tree.new_leaf(leaf_style.clone()).unwrap();
764 let c3 = tree.new_leaf(leaf_style).unwrap();
765 let root = tree.new_with_children(root_style, &[c1, c2, c3]).unwrap();
766
767 tree.compute_layout(
768 root,
769 tf::Size {
770 width: tf::AvailableSpace::Definite(900.0),
771 height: tf::AvailableSpace::Definite(300.0),
772 },
773 )
774 .unwrap();
775
776 let x1 = tree.layout(c1).unwrap().location.x;
777 let x2 = tree.layout(c2).unwrap().location.x;
778 let x3 = tree.layout(c3).unwrap().location.x;
779 let w1 = tree.layout(c1).unwrap().size.width;
780
781 assert_ne!(x1, x2, "columns 1 and 2 must not share an x-offset");
782 assert_ne!(x2, x3, "columns 2 and 3 must not share an x-offset");
783 assert!(
784 (x1 - 0.0).abs() < 0.5,
785 "column 1 should start at x=0, got {x1}"
786 );
787 assert!(
788 (x2 - 300.0).abs() < 1.0,
789 "column 2 should start at ~300px, got {x2}"
790 );
791 assert!(
792 (x3 - 600.0).abs() < 1.0,
793 "column 3 should start at ~600px, got {x3}"
794 );
795 assert!(
796 (w1 - 300.0).abs() < 1.0,
797 "each 1fr column should be ~300px wide, got {w1}"
798 );
799 }
800
801 #[test]
805 fn box_sizing_content_box_is_translated() {
806 let css = CssStyle {
807 box_sizing: Some(BoxSizing::ContentBox),
808 ..Default::default()
809 };
810 let s = to_taffy_style(&css, &ctx());
811 assert_eq!(s.box_sizing, tf::BoxSizing::ContentBox);
812 }
813
814 #[test]
815 fn box_sizing_defaults_to_border_box() {
816 let css = CssStyle::default();
817 let s = to_taffy_style(&css, &ctx());
818 assert_eq!(s.box_sizing, tf::BoxSizing::BorderBox);
819 }
820
821 #[test]
822 fn justify_items_is_translated_for_grid_children() {
823 let css = CssStyle {
824 display: Some(Display::Grid),
825 justify_items: Some(JustifyItems::Center),
826 ..Default::default()
827 };
828 let s = to_taffy_style(&css, &ctx());
829 assert_eq!(s.justify_items, Some(tf::AlignItems::Center));
830 }
831
832 #[test]
833 fn justify_self_is_translated() {
834 let css = CssStyle {
835 justify_self: Some(JustifySelf::End),
836 ..Default::default()
837 };
838 let s = to_taffy_style(&css, &ctx());
839 assert_eq!(s.justify_self, Some(tf::AlignSelf::End));
840 }
841
842 #[test]
843 fn justify_self_auto_falls_back_to_parent_justify_items() {
844 let css = CssStyle {
848 justify_self: Some(JustifySelf::Auto),
849 ..Default::default()
850 };
851 let s = to_taffy_style(&css, &ctx());
852 assert_eq!(s.justify_self, None);
853 }
854}