use gpui_engine::{
AlignContent, AlignItems, Display, EngineLayoutStyle, FlexDirection, FlexWrap, GridTemplate,
GridTemplateMinSize, Overflow, Position,
};
use gpui_types::{
AbsoluteLength, DefiniteLength, Edges, GridPlacement, Length, Pixels, Size,
round_stroke_to_device_pixel, round_to_device_pixel,
};
use std::{fmt::Debug, ops::Range};
use taffy::{
geometry::{Rect as TaffyRect, Size as TaffySize},
prelude::{max_content, min_content},
};
fn to_taffy_align_items(value: AlignItems) -> taffy::style::AlignItems {
match value {
AlignItems::Start => taffy::style::AlignItems::START,
AlignItems::End => taffy::style::AlignItems::END,
AlignItems::FlexStart => taffy::style::AlignItems::FLEX_START,
AlignItems::FlexEnd => taffy::style::AlignItems::FLEX_END,
AlignItems::Center => taffy::style::AlignItems::CENTER,
AlignItems::Baseline => taffy::style::AlignItems::BASELINE,
AlignItems::Stretch => taffy::style::AlignItems::STRETCH,
}
}
fn to_taffy_align_content(value: AlignContent) -> taffy::style::AlignContent {
match value {
AlignContent::Start => taffy::style::AlignContent::START,
AlignContent::End => taffy::style::AlignContent::END,
AlignContent::FlexStart => taffy::style::AlignContent::FLEX_START,
AlignContent::FlexEnd => taffy::style::AlignContent::FLEX_END,
AlignContent::Center => taffy::style::AlignContent::CENTER,
AlignContent::Stretch => taffy::style::AlignContent::STRETCH,
AlignContent::SpaceBetween => taffy::style::AlignContent::SPACE_BETWEEN,
AlignContent::SpaceEvenly => taffy::style::AlignContent::SPACE_EVENLY,
AlignContent::SpaceAround => taffy::style::AlignContent::SPACE_AROUND,
}
}
fn to_taffy_display(value: Display) -> taffy::style::Display {
match value {
Display::Block => taffy::style::Display::Block,
Display::Flex => taffy::style::Display::Flex,
Display::Grid => taffy::style::Display::Grid,
Display::None => taffy::style::Display::None,
}
}
fn to_taffy_flex_wrap(value: FlexWrap) -> taffy::style::FlexWrap {
match value {
FlexWrap::NoWrap => taffy::style::FlexWrap::NoWrap,
FlexWrap::Wrap => taffy::style::FlexWrap::Wrap,
FlexWrap::WrapReverse => taffy::style::FlexWrap::WrapReverse,
}
}
fn to_taffy_flex_direction(value: FlexDirection) -> taffy::style::FlexDirection {
match value {
FlexDirection::Row => taffy::style::FlexDirection::Row,
FlexDirection::Column => taffy::style::FlexDirection::Column,
FlexDirection::RowReverse => taffy::style::FlexDirection::RowReverse,
FlexDirection::ColumnReverse => taffy::style::FlexDirection::ColumnReverse,
}
}
fn to_taffy_overflow(value: Overflow) -> taffy::style::Overflow {
match value {
Overflow::Visible => taffy::style::Overflow::Visible,
Overflow::Clip => taffy::style::Overflow::Clip,
Overflow::Hidden => taffy::style::Overflow::Hidden,
Overflow::Scroll => taffy::style::Overflow::Scroll,
}
}
fn to_taffy_position(value: Position) -> taffy::style::Position {
match value {
Position::Relative => taffy::style::Position::Relative,
Position::Absolute => taffy::style::Position::Absolute,
}
}
pub(crate) fn to_taffy_style(
style: &EngineLayoutStyle,
rem_size: Pixels,
scale_factor: f32,
) -> taffy::style::Style {
style.to_taffy(rem_size, scale_factor)
}
fn border_widths_to_taffy(
widths: &Edges<AbsoluteLength>,
rem_size: Pixels,
scale_factor: f32,
) -> TaffyRect<taffy::style::LengthPercentage> {
let snap = |w: &AbsoluteLength| {
taffy::style::LengthPercentage::length(round_stroke_to_device_pixel(
w.to_pixels(rem_size).0,
scale_factor,
))
};
TaffyRect {
top: snap(&widths.top),
right: snap(&widths.right),
bottom: snap(&widths.bottom),
left: snap(&widths.left),
}
}
trait ToTaffy<Output> {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> Output;
}
impl ToTaffy<taffy::style::Style> for EngineLayoutStyle {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Style {
use taffy::style_helpers::{fr, length, minmax, repeat};
fn to_grid_line(placement: &Range<GridPlacement>) -> taffy::Line<taffy::GridPlacement> {
taffy::Line {
start: placement.start.into(),
end: placement.end.into(),
}
}
fn to_grid_repeat<T: taffy::style::CheapCloneStr>(
unit: &Option<GridTemplate>,
) -> Vec<taffy::GridTemplateComponent<T>> {
unit.map(|template| {
match template.min_size {
GridTemplateMinSize::Zero => {
vec![repeat(
template.repeat,
vec![minmax(length(0.0_f32), fr(1.0_f32))],
)]
}
GridTemplateMinSize::MinContent => {
vec![repeat(
template.repeat,
vec![minmax(min_content(), fr(1.0_f32))],
)]
}
GridTemplateMinSize::MaxContent => {
vec![repeat(
template.repeat,
vec![minmax(length(0.0_f32), max_content())],
)]
}
}
})
.unwrap_or_default()
}
taffy::style::Style {
display: to_taffy_display(self.display),
overflow: taffy::geometry::Point {
x: to_taffy_overflow(self.overflow.x),
y: to_taffy_overflow(self.overflow.y),
},
scrollbar_width: self.scrollbar_width.to_taffy(rem_size, scale_factor),
position: to_taffy_position(self.position),
inset: self.inset.to_taffy(rem_size, scale_factor),
size: self.size.to_taffy(rem_size, scale_factor),
min_size: self.min_size.to_taffy(rem_size, scale_factor),
max_size: self.max_size.to_taffy(rem_size, scale_factor),
aspect_ratio: self.aspect_ratio,
margin: self.margin.to_taffy(rem_size, scale_factor),
padding: self.padding.to_taffy(rem_size, scale_factor),
border: border_widths_to_taffy(&self.border_widths, rem_size, scale_factor),
align_items: self.align_items.map(to_taffy_align_items),
align_self: self.align_self.map(to_taffy_align_items),
align_content: self.align_content.map(to_taffy_align_content),
justify_content: self.justify_content.map(to_taffy_align_content),
gap: self.gap.to_taffy(rem_size, scale_factor),
flex_direction: to_taffy_flex_direction(self.flex_direction),
flex_wrap: to_taffy_flex_wrap(self.flex_wrap),
flex_basis: self.flex_basis.to_taffy(rem_size, scale_factor),
flex_grow: self.flex_grow,
flex_shrink: self.flex_shrink,
grid_template_rows: to_grid_repeat(&self.grid_rows),
grid_template_columns: to_grid_repeat(&self.grid_cols),
grid_row: self
.grid_location
.as_ref()
.map(|location| to_grid_line(&location.row))
.unwrap_or_default(),
grid_column: self
.grid_location
.as_ref()
.map(|location| to_grid_line(&location.column))
.unwrap_or_default(),
..Default::default()
}
}
}
impl ToTaffy<f32> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> f32 {
round_to_device_pixel(self.to_pixels(rem_size).0, scale_factor)
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for Length {
fn to_taffy(
&self,
rem_size: Pixels,
scale_factor: f32,
) -> taffy::prelude::LengthPercentageAuto {
match self {
Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
Length::Auto => taffy::prelude::LengthPercentageAuto::auto(),
}
}
}
impl ToTaffy<taffy::style::Dimension> for Length {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::prelude::Dimension {
match self {
Length::Definite(length) => length.to_taffy(rem_size, scale_factor),
Length::Auto => taffy::prelude::Dimension::auto(),
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
match self {
DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentage::percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
match self {
DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
DefiniteLength::Fraction(fraction) => {
taffy::style::LengthPercentageAuto::percent(*fraction)
}
}
}
}
impl ToTaffy<taffy::style::Dimension> for DefiniteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
match self {
DefiniteLength::Absolute(length) => length.to_taffy(rem_size, scale_factor),
DefiniteLength::Fraction(fraction) => taffy::style::Dimension::percent(*fraction),
}
}
}
impl ToTaffy<taffy::style::LengthPercentage> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentage {
taffy::style::LengthPercentage::length(self.to_taffy(rem_size, scale_factor))
}
}
impl ToTaffy<taffy::style::LengthPercentageAuto> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::LengthPercentageAuto {
taffy::style::LengthPercentageAuto::length(self.to_taffy(rem_size, scale_factor))
}
}
impl ToTaffy<taffy::style::Dimension> for AbsoluteLength {
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> taffy::style::Dimension {
taffy::style::Dimension::length(self.to_taffy(rem_size, scale_factor))
}
}
impl<T, U> ToTaffy<TaffySize<U>> for Size<T>
where
T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
{
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffySize<U> {
TaffySize {
width: self.width.to_taffy(rem_size, scale_factor),
height: self.height.to_taffy(rem_size, scale_factor),
}
}
}
impl<T, U> ToTaffy<TaffyRect<U>> for Edges<T>
where
T: ToTaffy<U> + Clone + Debug + Default + PartialEq,
{
fn to_taffy(&self, rem_size: Pixels, scale_factor: f32) -> TaffyRect<U> {
TaffyRect {
top: self.top.to_taffy(rem_size, scale_factor),
right: self.right.to_taffy(rem_size, scale_factor),
bottom: self.bottom.to_taffy(rem_size, scale_factor),
left: self.left.to_taffy(rem_size, scale_factor),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn border_widths_to_taffy_use_stroke_snapping() {
let border_widths = Edges {
top: Pixels(0.0).into(),
right: Pixels(0.4).into(),
bottom: Pixels(0.5).into(),
left: Pixels(1.6).into(),
};
let taffy_border = border_widths_to_taffy(&border_widths, Pixels(16.0), 1.0);
assert_eq!(
taffy_border.top,
taffy::style::LengthPercentage::length(0.0)
);
assert_eq!(
taffy_border.right,
taffy::style::LengthPercentage::length(1.0)
);
assert_eq!(
taffy_border.bottom,
taffy::style::LengthPercentage::length(1.0)
);
assert_eq!(
taffy_border.left,
taffy::style::LengthPercentage::length(2.0)
);
}
}