Skip to main content

native/guppy_nif/src/bridge_view/style.rs

use crate::ir::{
    AlignContentStyle, AlignItemsStyle, BorderLineStyle, BorderRadiusAxis, BoxShadowSpec,
    ColorToken, DisplayStyle, DivStyle, FlexDirectionStyle, FlexItemStyle, FlexWrapStyle,
    FontSizeStyle, FontStyleValue, FontWeightStyle, JustifyContentStyle, LineHeightStyle,
    LinearGradientStop, MouseCursorStyle, OverflowStyle, PositionStyle, ShadowStyle, StyleAxis,
    StyleColor, StyleLength, StyleOp, TextAlignStyle, TextDecorationLineStyle, TextDecorationStyle,
    TextOverflowStyle, VisibilityStyle, WhiteSpaceStyle,
};
use gpui::{
    BoxShadow, DefiniteLength, FontFallbacks, FontFeatures, FontStyle, FontWeight, HighlightStyle,
    Length, StatefulInteractiveElement, StrikethroughStyle, StyleRefinement, Styled,
    UnderlineStyle, linear_color_stop, linear_gradient, pattern_slash, point, px, relative, rems,
    rgb,
};
use std::sync::Arc;

pub(crate) fn apply_semantic_focus_visible_affordance<E>(element: E, enabled: bool) -> E
where
    E: Styled + StatefulInteractiveElement,
{
    if enabled {
        element.border_1().border_color(rgb(0x60a5fa))
    } else {
        element
    }
}

pub(crate) fn apply_div_style<E>(mut element: E, style: &DivStyle) -> E
where
    E: Styled + StatefulInteractiveElement,
{
    for op in style.iter() {
        element = match apply_refinement_supported_style_op(element, op) {
            StyleApplication::Applied(element) => element,
            StyleApplication::Unsupported(element) => apply_div_only_style_op(element, op),
        };
    }

    element
}

enum StyleApplication<T> {
    Applied(T),
    Unsupported(T),
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LengthStyleProperty {
    Width,
    Height,
    Size,
    MinWidth,
    MinHeight,
    MaxWidth,
    MaxHeight,
}

#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum RefinementStyleSupport {
    Supported,
    Unsupported,
}

fn refinement_style_support(op: &StyleOp) -> RefinementStyleSupport {
    // Refinement styles are used for pseudo-state overlays such as hover/focus. These ops are
    // deliberately element-only because applying them from a refinement can relayout, reposition,
    // resize, or mutate interactive scroll behavior instead of only changing state presentation.
    match op {
        StyleOp::Grid
        | StyleOp::Flex
        | StyleOp::FlexCol
        | StyleOp::FlexRow
        | StyleOp::FlexWrap
        | StyleOp::FlexNowrap
        | StyleOp::FlexNone
        | StyleOp::FlexAuto
        | StyleOp::FlexGrow
        | StyleOp::FlexShrink
        | StyleOp::FlexShrink0
        | StyleOp::Flex1
        | StyleOp::ColSpanFull
        | StyleOp::RowSpanFull
        | StyleOp::SizeFull
        | StyleOp::WFull
        | StyleOp::HFull
        | StyleOp::W32
        | StyleOp::W64
        | StyleOp::W96
        | StyleOp::H32
        | StyleOp::MinW32
        | StyleOp::MinH0
        | StyleOp::MinHFull
        | StyleOp::MaxW64
        | StyleOp::MaxW96
        | StyleOp::MaxWFull
        | StyleOp::MaxH32
        | StyleOp::MaxH96
        | StyleOp::MaxHFull
        | StyleOp::Gap1
        | StyleOp::Gap2
        | StyleOp::Gap4
        | StyleOp::P1
        | StyleOp::P2
        | StyleOp::P4
        | StyleOp::P6
        | StyleOp::P8
        | StyleOp::Px2
        | StyleOp::Py2
        | StyleOp::Pt2
        | StyleOp::Pr2
        | StyleOp::Pb2
        | StyleOp::Pl2
        | StyleOp::M2
        | StyleOp::Mx2
        | StyleOp::My2
        | StyleOp::Mt2
        | StyleOp::Mr2
        | StyleOp::Mb2
        | StyleOp::Ml2
        | StyleOp::Relative
        | StyleOp::Absolute
        | StyleOp::Top0
        | StyleOp::Right0
        | StyleOp::Bottom0
        | StyleOp::Left0
        | StyleOp::Inset0
        | StyleOp::Top1
        | StyleOp::Right1
        | StyleOp::Top2
        | StyleOp::Right2
        | StyleOp::Bottom2
        | StyleOp::Left2
        | StyleOp::OverflowScroll
        | StyleOp::OverflowXScroll
        | StyleOp::OverflowYScroll
        | StyleOp::OverflowHidden
        | StyleOp::OverflowXHidden
        | StyleOp::OverflowYHidden
        | StyleOp::Padding { .. }
        | StyleOp::Margin { .. }
        | StyleOp::Gap { .. }
        | StyleOp::Width(_)
        | StyleOp::Height(_)
        | StyleOp::Size(_)
        | StyleOp::MinWidth(_)
        | StyleOp::MinHeight(_)
        | StyleOp::MaxWidth(_)
        | StyleOp::MaxHeight(_)
        | StyleOp::AspectRatio(_)
        | StyleOp::Position(_)
        | StyleOp::Inset { .. }
        | StyleOp::Display(_)
        | StyleOp::Overflow { .. }
        | StyleOp::AllowConcurrentScroll(_)
        | StyleOp::RestrictScrollToAxis(_)
        | StyleOp::FlexDirection(_)
        | StyleOp::FlexWrapValue(_)
        | StyleOp::FlexItem(_)
        | StyleOp::FlexBasis(_)
        | StyleOp::FlexGrowValue(_)
        | StyleOp::FlexShrinkValue(_)
        | StyleOp::AlignItems(_)
        | StyleOp::AlignSelf(_)
        | StyleOp::JustifyContent(_)
        | StyleOp::AlignContent(_)
        | StyleOp::GridCols(_)
        | StyleOp::GridRows(_)
        | StyleOp::ColStart(_)
        | StyleOp::ColStartAuto
        | StyleOp::ColEnd(_)
        | StyleOp::ColEndAuto
        | StyleOp::RowStart(_)
        | StyleOp::RowStartAuto
        | StyleOp::RowEnd(_)
        | StyleOp::RowEndAuto
        | StyleOp::ColSpan(_)
        | StyleOp::RowSpan(_)
        | StyleOp::ScrollbarWidthPx(_)
        | StyleOp::ScrollbarWidthRem(_) => RefinementStyleSupport::Unsupported,
        StyleOp::TextLeft
        | StyleOp::TextCenter
        | StyleOp::TextRight
        | StyleOp::WhitespaceNormal
        | StyleOp::WhitespaceNowrap
        | StyleOp::Truncate
        | StyleOp::TextEllipsis
        | StyleOp::LineClamp2
        | StyleOp::LineClamp3
        | StyleOp::LineClamp(_)
        | StyleOp::TextXs
        | StyleOp::TextSm
        | StyleOp::TextBase
        | StyleOp::TextLg
        | StyleOp::TextXl
        | StyleOp::Text2xl
        | StyleOp::Text3xl
        | StyleOp::LeadingNone
        | StyleOp::LeadingTight
        | StyleOp::LeadingSnug
        | StyleOp::LeadingNormal
        | StyleOp::LeadingRelaxed
        | StyleOp::LeadingLoose
        | StyleOp::FontThin
        | StyleOp::FontExtralight
        | StyleOp::FontLight
        | StyleOp::FontNormal
        | StyleOp::FontMedium
        | StyleOp::FontSemibold
        | StyleOp::FontBold
        | StyleOp::FontExtrabold
        | StyleOp::FontBlack
        | StyleOp::Italic
        | StyleOp::NotItalic
        | StyleOp::Underline
        | StyleOp::LineThrough
        | StyleOp::ItemsStart
        | StyleOp::ItemsCenter
        | StyleOp::ItemsEnd
        | StyleOp::JustifyStart
        | StyleOp::JustifyCenter
        | StyleOp::JustifyEnd
        | StyleOp::JustifyBetween
        | StyleOp::JustifyAround
        | StyleOp::CursorPointer
        | StyleOp::RoundedSm
        | StyleOp::RoundedMd
        | StyleOp::RoundedLg
        | StyleOp::RoundedXl
        | StyleOp::Rounded2xl
        | StyleOp::RoundedFull
        | StyleOp::Border1
        | StyleOp::Border2
        | StyleOp::BorderDashed
        | StyleOp::BorderT1
        | StyleOp::BorderR1
        | StyleOp::BorderB1
        | StyleOp::BorderL1
        | StyleOp::ShadowSm
        | StyleOp::ShadowMd
        | StyleOp::ShadowLg
        | StyleOp::Visibility(_)
        | StyleOp::Cursor(_)
        | StyleOp::BorderWidth { .. }
        | StyleOp::BorderRadius { .. }
        | StyleOp::BorderStyle(_)
        | StyleOp::Shadow(_)
        | StyleOp::BoxShadow(_)
        | StyleOp::TextAlign(_)
        | StyleOp::WhiteSpace(_)
        | StyleOp::TextOverflow(_)
        | StyleOp::FontSize(_)
        | StyleOp::TextSize(_)
        | StyleOp::LineHeight(_)
        | StyleOp::LineHeightLength(_)
        | StyleOp::FontWeight(_)
        | StyleOp::FontWeightValue(_)
        | StyleOp::FontStyle(_)
        | StyleOp::FontFamily(_)
        | StyleOp::FontFallbacks(_)
        | StyleOp::FontFeatures(_)
        | StyleOp::TextDecoration(_)
        | StyleOp::TextDecorationColor(_)
        | StyleOp::TextDecorationColorHex(_)
        | StyleOp::TextDecorationLineStyle(_)
        | StyleOp::TextDecorationThickness(_)
        | StyleOp::StrikethroughColor(_)
        | StyleOp::StrikethroughColorHex(_)
        | StyleOp::StrikethroughThickness(_)
        | StyleOp::Bg(_)
        | StyleOp::TextColor(_)
        | StyleOp::TextBg(_)
        | StyleOp::BorderColor(_)
        | StyleOp::BgHex(_)
        | StyleOp::TextColorHex(_)
        | StyleOp::TextBgHex(_)
        | StyleOp::BorderColorHex(_)
        | StyleOp::BgLinearGradient { .. }
        | StyleOp::BgPatternSlash(_)
        | StyleOp::Debug(_)
        | StyleOp::DebugBelow(_)
        | StyleOp::Opacity(_)
        | StyleOp::WPx(_)
        | StyleOp::WRem(_)
        | StyleOp::WFrac(_)
        | StyleOp::HPx(_)
        | StyleOp::HRem(_)
        | StyleOp::HFrac(_) => RefinementStyleSupport::Supported,
    }
}

fn apply_refinement_supported_style_op<T>(style: T, op: &StyleOp) -> StyleApplication<T>
where
    T: Styled,
{
    if refinement_style_support(op) != RefinementStyleSupport::Supported {
        return StyleApplication::Unsupported(style);
    }

    let style = match op {
        StyleOp::TextLeft => style.text_left(),
        StyleOp::TextCenter => style.text_center(),
        StyleOp::TextRight => style.text_right(),
        StyleOp::WhitespaceNormal => style.whitespace_normal(),
        StyleOp::WhitespaceNowrap => style.whitespace_nowrap(),
        StyleOp::Truncate => style.truncate(),
        StyleOp::TextEllipsis => style.text_ellipsis(),
        StyleOp::LineClamp2 => style.line_clamp(2),
        StyleOp::LineClamp3 => style.line_clamp(3),
        StyleOp::LineClamp(value) => style.line_clamp(usize::from(*value)),
        StyleOp::TextXs => style.text_xs(),
        StyleOp::TextSm => style.text_sm(),
        StyleOp::TextBase => style.text_base(),
        StyleOp::TextLg => style.text_lg(),
        StyleOp::TextXl => style.text_xl(),
        StyleOp::Text2xl => style.text_2xl(),
        StyleOp::Text3xl => style.text_3xl(),
        StyleOp::LeadingNone => style.line_height(relative(1.0)),
        StyleOp::LeadingTight => style.line_height(relative(1.25)),
        StyleOp::LeadingSnug => style.line_height(relative(1.375)),
        StyleOp::LeadingNormal => style.line_height(relative(1.5)),
        StyleOp::LeadingRelaxed => style.line_height(relative(1.625)),
        StyleOp::LeadingLoose => style.line_height(relative(2.0)),
        StyleOp::FontThin => style.font_weight(FontWeight::THIN),
        StyleOp::FontExtralight => style.font_weight(FontWeight::EXTRA_LIGHT),
        StyleOp::FontLight => style.font_weight(FontWeight::LIGHT),
        StyleOp::FontNormal => style.font_weight(FontWeight::NORMAL),
        StyleOp::FontMedium => style.font_weight(FontWeight::MEDIUM),
        StyleOp::FontSemibold => style.font_weight(FontWeight::SEMIBOLD),
        StyleOp::FontBold => style.font_weight(FontWeight::BOLD),
        StyleOp::FontExtrabold => style.font_weight(FontWeight::EXTRA_BOLD),
        StyleOp::FontBlack => style.font_weight(FontWeight::BLACK),
        StyleOp::Italic => style.italic(),
        StyleOp::NotItalic => style.not_italic(),
        StyleOp::Underline => style.underline(),
        StyleOp::LineThrough => style.line_through(),
        StyleOp::ItemsStart => style.items_start(),
        StyleOp::ItemsCenter => style.items_center(),
        StyleOp::ItemsEnd => style.items_end(),
        StyleOp::JustifyStart => style.justify_start(),
        StyleOp::JustifyCenter => style.justify_center(),
        StyleOp::JustifyEnd => style.justify_end(),
        StyleOp::JustifyBetween => style.justify_between(),
        StyleOp::JustifyAround => style.justify_around(),
        StyleOp::CursorPointer => style.cursor_pointer(),
        StyleOp::RoundedSm => style.rounded_sm(),
        StyleOp::RoundedMd => style.rounded_md(),
        StyleOp::RoundedLg => style.rounded_lg(),
        StyleOp::RoundedXl => style.rounded_xl(),
        StyleOp::Rounded2xl => style.rounded_2xl(),
        StyleOp::RoundedFull => style.rounded_full(),
        StyleOp::Border1 => style.border_1(),
        StyleOp::Border2 => style.border_2(),
        StyleOp::BorderDashed => style.border_dashed(),
        StyleOp::BorderT1 => style.border_t_1(),
        StyleOp::BorderR1 => style.border_r_1(),
        StyleOp::BorderB1 => style.border_b_1(),
        StyleOp::BorderL1 => style.border_l_1(),
        StyleOp::ShadowSm => style.shadow_sm(),
        StyleOp::ShadowMd => style.shadow_md(),
        StyleOp::ShadowLg => style.shadow_lg(),
        StyleOp::Bg(color) => style.bg(color_token_to_color(*color)),
        StyleOp::TextColor(color) => style.text_color(color_token_to_color(*color)),
        StyleOp::TextBg(color) => style.text_bg(color_token_to_color(*color)),
        StyleOp::BorderColor(color) => style.border_color(color_token_to_color(*color)),
        StyleOp::BgHex(value) => style.bg(hex_color_to_color(*value)),
        StyleOp::TextColorHex(value) => style.text_color(hex_color_to_color(*value)),
        StyleOp::TextBgHex(value) => style.text_bg(hex_color_to_color(*value)),
        StyleOp::BorderColorHex(value) => style.border_color(hex_color_to_color(*value)),
        StyleOp::BgLinearGradient { angle, from, to } => style.bg(linear_gradient(
            *angle,
            linear_gradient_stop_to_gpui(from),
            linear_gradient_stop_to_gpui(to),
        )),
        StyleOp::BgPatternSlash(pattern) => style.bg(pattern_slash(
            style_color_to_color(&pattern.color),
            pattern.width,
            pattern.interval,
        )),
        StyleOp::Debug(value) => apply_debug(style, *value),
        StyleOp::DebugBelow(value) => apply_debug_below(style, *value),
        StyleOp::Opacity(value) => style.opacity(*value),
        StyleOp::Visibility(visibility) => apply_visibility(style, *visibility),
        StyleOp::Cursor(cursor) => apply_cursor(style, *cursor),
        StyleOp::BorderWidth { axis, length } => apply_border_width(style, *axis, *length),
        StyleOp::BorderRadius { axis, length } => apply_border_radius(style, *axis, *length),
        StyleOp::BorderStyle(border_style) => apply_border_style(style, *border_style),
        StyleOp::Shadow(shadow) => apply_shadow(style, *shadow),
        StyleOp::BoxShadow(shadows) => apply_box_shadow(style, shadows),
        StyleOp::TextAlign(align) => apply_text_align(style, *align),
        StyleOp::WhiteSpace(white_space) => apply_white_space(style, *white_space),
        StyleOp::TextOverflow(overflow) => apply_text_overflow(style, *overflow),
        StyleOp::FontSize(size) => apply_font_size(style, *size),
        StyleOp::TextSize(size) => apply_text_size(style, *size),
        StyleOp::LineHeight(line_height) => apply_line_height(style, *line_height),
        StyleOp::LineHeightLength(length) => apply_line_height_length(style, *length),
        StyleOp::FontWeight(weight) => apply_font_weight(style, *weight),
        StyleOp::FontWeightValue(value) => apply_font_weight_value(style, *value),
        StyleOp::FontStyle(font_style) => apply_font_style(style, *font_style),
        StyleOp::FontFamily(family) => style.font_family(family.clone()),
        StyleOp::FontFallbacks(fallbacks) => apply_font_fallbacks(style, fallbacks),
        StyleOp::FontFeatures(features) => apply_font_features(style, features),
        StyleOp::TextDecoration(decoration) => apply_text_decoration(style, *decoration),
        StyleOp::TextDecorationColor(color) => {
            style.text_decoration_color(color_token_to_color(*color))
        }
        StyleOp::TextDecorationColorHex(value) => {
            style.text_decoration_color(hex_color_to_color(*value))
        }
        StyleOp::TextDecorationLineStyle(line_style) => {
            apply_text_decoration_line_style(style, *line_style)
        }
        StyleOp::TextDecorationThickness(value) => apply_text_decoration_thickness(style, *value),
        StyleOp::StrikethroughColor(color) => apply_strikethrough_color(style, *color),
        StyleOp::StrikethroughColorHex(value) => {
            apply_strikethrough_color_value(style, hex_color_to_color(*value))
        }
        StyleOp::StrikethroughThickness(value) => apply_strikethrough_thickness(style, *value),
        StyleOp::WPx(value) => style.w(px(*value)),
        StyleOp::WRem(value) => style.w(rems(*value)),
        StyleOp::WFrac(value) => style.w(relative(*value)),
        StyleOp::HPx(value) => style.h(px(*value)),
        StyleOp::HRem(value) => style.h(rems(*value)),
        StyleOp::HFrac(value) => style.h(relative(*value)),
        _ => return StyleApplication::Unsupported(style),
    };

    StyleApplication::Applied(style)
}

fn apply_div_only_style_op<E>(element: E, op: &StyleOp) -> E
where
    E: Styled + StatefulInteractiveElement,
{
    match op {
        StyleOp::Grid => element.grid(),
        StyleOp::Flex => element.flex(),
        StyleOp::FlexCol => element.flex_col(),
        StyleOp::FlexRow => element.flex_row(),
        StyleOp::FlexWrap => element.flex_wrap(),
        StyleOp::FlexNowrap => element.flex_nowrap(),
        StyleOp::FlexNone => element.flex_none(),
        StyleOp::FlexAuto => element.flex_auto(),
        StyleOp::FlexGrow => element.flex_grow(),
        StyleOp::FlexShrink => element.flex_shrink(),
        StyleOp::FlexShrink0 => element.flex_shrink_0(),
        StyleOp::Flex1 => element.flex_1(),
        StyleOp::ColSpanFull => element.col_span_full(),
        StyleOp::RowSpanFull => element.row_span_full(),
        StyleOp::SizeFull => element.size_full(),
        StyleOp::WFull => element.w_full(),
        StyleOp::HFull => element.h_full(),
        StyleOp::W32 => element.w_32(),
        StyleOp::W64 => element.w_64(),
        StyleOp::W96 => element.w_96(),
        StyleOp::H32 => element.h_32(),
        StyleOp::MinW32 => element.min_w_32(),
        StyleOp::MinH0 => element.min_h_0(),
        StyleOp::MinHFull => element.min_h_full(),
        StyleOp::MaxW64 => element.max_w_64(),
        StyleOp::MaxW96 => element.max_w_96(),
        StyleOp::MaxWFull => element.max_w_full(),
        StyleOp::MaxH32 => element.max_h_32(),
        StyleOp::MaxH96 => element.max_h_96(),
        StyleOp::MaxHFull => element.max_h_full(),
        StyleOp::Gap1 => element.gap_1(),
        StyleOp::Gap2 => element.gap_2(),
        StyleOp::Gap4 => element.gap_4(),
        StyleOp::P1 => element.p_1(),
        StyleOp::P2 => element.p_2(),
        StyleOp::P4 => element.p_4(),
        StyleOp::P6 => element.p_6(),
        StyleOp::P8 => element.p_8(),
        StyleOp::Px2 => element.px_2(),
        StyleOp::Py2 => element.py_2(),
        StyleOp::Pt2 => element.pt_2(),
        StyleOp::Pr2 => element.pr_2(),
        StyleOp::Pb2 => element.pb_2(),
        StyleOp::Pl2 => element.pl_2(),
        StyleOp::M2 => element.m_2(),
        StyleOp::Mx2 => element.mx_2(),
        StyleOp::My2 => element.my_2(),
        StyleOp::Mt2 => element.mt_2(),
        StyleOp::Mr2 => element.mr_2(),
        StyleOp::Mb2 => element.mb_2(),
        StyleOp::Ml2 => element.ml_2(),
        StyleOp::Relative => element.relative(),
        StyleOp::Absolute => element.absolute(),
        StyleOp::Top0 => element.top_0(),
        StyleOp::Right0 => element.right_0(),
        StyleOp::Bottom0 => element.bottom_0(),
        StyleOp::Left0 => element.left_0(),
        StyleOp::Inset0 => element.inset_0(),
        StyleOp::Top1 => element.top_1(),
        StyleOp::Right1 => element.right_1(),
        StyleOp::Top2 => element.top_2(),
        StyleOp::Right2 => element.right_2(),
        StyleOp::Bottom2 => element.bottom_2(),
        StyleOp::Left2 => element.left_2(),
        StyleOp::OverflowScroll => element.overflow_scroll(),
        StyleOp::OverflowXScroll => element.overflow_x_scroll(),
        StyleOp::OverflowYScroll => element.overflow_y_scroll(),
        StyleOp::OverflowHidden => element.overflow_hidden(),
        StyleOp::OverflowXHidden => element.overflow_x_hidden(),
        StyleOp::OverflowYHidden => element.overflow_y_hidden(),
        StyleOp::GridCols(value) => element.grid_cols(*value),
        StyleOp::GridRows(value) => element.grid_rows(*value),
        StyleOp::ColStart(value) => element.col_start(*value),
        StyleOp::ColStartAuto => element.col_start_auto(),
        StyleOp::ColEnd(value) => element.col_end(*value),
        StyleOp::ColEndAuto => element.col_end_auto(),
        StyleOp::RowStart(value) => element.row_start(*value),
        StyleOp::RowStartAuto => element.row_start_auto(),
        StyleOp::RowEnd(value) => element.row_end(*value),
        StyleOp::RowEndAuto => element.row_end_auto(),
        StyleOp::ColSpan(value) => element.col_span(*value),
        StyleOp::RowSpan(value) => element.row_span(*value),
        StyleOp::ScrollbarWidthPx(value) => element.scrollbar_width(px(*value)),
        StyleOp::ScrollbarWidthRem(value) => element.scrollbar_width(rems(*value)),
        StyleOp::Padding { axis, length } => apply_padding(element, *axis, *length),
        StyleOp::Margin { axis, length } => apply_margin(element, *axis, *length),
        StyleOp::Gap { axis, length } => apply_gap(element, *axis, *length),
        StyleOp::Width(length) => apply_length_style(element, LengthStyleProperty::Width, *length),
        StyleOp::Height(length) => {
            apply_length_style(element, LengthStyleProperty::Height, *length)
        }
        StyleOp::Size(length) => apply_length_style(element, LengthStyleProperty::Size, *length),
        StyleOp::MinWidth(length) => {
            apply_length_style(element, LengthStyleProperty::MinWidth, *length)
        }
        StyleOp::MinHeight(length) => {
            apply_length_style(element, LengthStyleProperty::MinHeight, *length)
        }
        StyleOp::MaxWidth(length) => {
            apply_length_style(element, LengthStyleProperty::MaxWidth, *length)
        }
        StyleOp::MaxHeight(length) => {
            apply_length_style(element, LengthStyleProperty::MaxHeight, *length)
        }
        StyleOp::AspectRatio(value) => apply_aspect_ratio(element, *value),
        StyleOp::Position(position) => apply_position(element, *position),
        StyleOp::Inset { axis, length } => apply_inset(element, *axis, *length),
        StyleOp::Display(display) => apply_display(element, *display),
        StyleOp::Visibility(visibility) => apply_visibility(element, *visibility),
        StyleOp::Overflow { axis, behavior } => apply_overflow(element, *axis, *behavior),
        StyleOp::AllowConcurrentScroll(value) => apply_allow_concurrent_scroll(element, *value),
        StyleOp::RestrictScrollToAxis(value) => apply_restrict_scroll_to_axis(element, *value),
        StyleOp::Cursor(cursor) => apply_cursor(element, *cursor),
        StyleOp::BorderWidth { axis, length } => apply_border_width(element, *axis, *length),
        StyleOp::BorderRadius { axis, length } => apply_border_radius(element, *axis, *length),
        StyleOp::BorderStyle(border_style) => apply_border_style(element, *border_style),
        StyleOp::Shadow(shadow) => apply_shadow(element, *shadow),
        StyleOp::FlexDirection(direction) => apply_flex_direction(element, *direction),
        StyleOp::FlexWrapValue(wrap) => apply_flex_wrap(element, *wrap),
        StyleOp::FlexItem(item) => apply_flex_item(element, *item),
        StyleOp::FlexBasis(length) => element.flex_basis(style_length_to_length(*length)),
        StyleOp::FlexGrowValue(value) => apply_flex_grow(element, *value),
        StyleOp::FlexShrinkValue(value) => apply_flex_shrink(element, *value),
        StyleOp::AlignItems(align) => apply_align_items(element, *align),
        StyleOp::AlignSelf(align) => apply_align_self(element, *align),
        StyleOp::JustifyContent(justify) => apply_justify_content(element, *justify),
        StyleOp::AlignContent(align) => apply_align_content(element, *align),
        _ => element,
    }
}

fn apply_padding<E>(element: E, axis: StyleAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_definite(length);

    match axis {
        StyleAxis::All => element.p(length),
        StyleAxis::X => element.px(length),
        StyleAxis::Y => element.py(length),
        StyleAxis::Top => element.pt(length),
        StyleAxis::Right => element.pr(length),
        StyleAxis::Bottom => element.pb(length),
        StyleAxis::Left => element.pl(length),
    }
}

fn apply_margin<E>(element: E, axis: StyleAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_length(length);

    match axis {
        StyleAxis::All => element.m(length),
        StyleAxis::X => element.mx(length),
        StyleAxis::Y => element.my(length),
        StyleAxis::Top => element.mt(length),
        StyleAxis::Right => element.mr(length),
        StyleAxis::Bottom => element.mb(length),
        StyleAxis::Left => element.ml(length),
    }
}

fn apply_gap<E>(element: E, axis: StyleAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_definite(length);

    match axis {
        StyleAxis::All => element.gap(length),
        StyleAxis::X => element.gap_x(length),
        StyleAxis::Y => element.gap_y(length),
        _ => element,
    }
}

fn apply_length_style<E>(element: E, property: LengthStyleProperty, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_length(length);

    match property {
        LengthStyleProperty::Width => element.w(length),
        LengthStyleProperty::Height => element.h(length),
        LengthStyleProperty::Size => element.size(length),
        LengthStyleProperty::MinWidth => element.min_w(length),
        LengthStyleProperty::MinHeight => element.min_h(length),
        LengthStyleProperty::MaxWidth => element.max_w(length),
        LengthStyleProperty::MaxHeight => element.max_h(length),
    }
}

fn apply_aspect_ratio<E>(mut element: E, value: f32) -> E
where
    E: Styled,
{
    element.style().aspect_ratio = Some(value);
    element
}

fn apply_position<E>(element: E, position: PositionStyle) -> E
where
    E: Styled,
{
    match position {
        PositionStyle::Relative => element.relative(),
        PositionStyle::Absolute => element.absolute(),
    }
}

fn apply_inset<E>(element: E, axis: StyleAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_length(length);

    match axis {
        StyleAxis::All => element.inset(length),
        StyleAxis::Top => element.top(length),
        StyleAxis::Right => element.right(length),
        StyleAxis::Bottom => element.bottom(length),
        StyleAxis::Left => element.left(length),
        _ => element,
    }
}

fn apply_display<E>(element: E, display: DisplayStyle) -> E
where
    E: Styled,
{
    match display {
        DisplayStyle::Block => element.block(),
        DisplayStyle::Flex => element.flex(),
        DisplayStyle::Grid => element.grid(),
        DisplayStyle::None => element.hidden(),
    }
}

fn apply_visibility<E>(element: E, visibility: VisibilityStyle) -> E
where
    E: Styled,
{
    match visibility {
        VisibilityStyle::Visible => element.visible(),
        VisibilityStyle::Hidden => element.invisible(),
    }
}

fn apply_overflow<E>(mut element: E, axis: StyleAxis, overflow: OverflowStyle) -> E
where
    E: Styled,
{
    let overflow = match overflow {
        OverflowStyle::Visible => gpui::Overflow::Visible,
        OverflowStyle::Clip => gpui::Overflow::Clip,
        OverflowStyle::Hidden => gpui::Overflow::Hidden,
        OverflowStyle::Scroll => gpui::Overflow::Scroll,
    };

    match axis {
        StyleAxis::All => {
            element.style().overflow.x = Some(overflow);
            element.style().overflow.y = Some(overflow);
        }
        StyleAxis::X => element.style().overflow.x = Some(overflow),
        StyleAxis::Y => element.style().overflow.y = Some(overflow),
        _ => {}
    }

    element
}

fn apply_allow_concurrent_scroll<E>(mut element: E, value: bool) -> E
where
    E: Styled,
{
    element.style().allow_concurrent_scroll = Some(value);
    element
}

fn apply_restrict_scroll_to_axis<E>(mut element: E, value: bool) -> E
where
    E: Styled,
{
    element.style().restrict_scroll_to_axis = Some(value);
    element
}

fn apply_cursor<E>(element: E, cursor: MouseCursorStyle) -> E
where
    E: Styled,
{
    element.cursor(mouse_cursor_to_gpui(cursor))
}

fn mouse_cursor_to_gpui(cursor: MouseCursorStyle) -> gpui::CursorStyle {
    match cursor {
        MouseCursorStyle::Default => gpui::CursorStyle::Arrow,
        MouseCursorStyle::Pointer => gpui::CursorStyle::PointingHand,
        MouseCursorStyle::Text => gpui::CursorStyle::IBeam,
        MouseCursorStyle::Move => gpui::CursorStyle::ClosedHand,
        MouseCursorStyle::NotAllowed => gpui::CursorStyle::OperationNotAllowed,
        MouseCursorStyle::ContextMenu => gpui::CursorStyle::ContextualMenu,
        MouseCursorStyle::Crosshair => gpui::CursorStyle::Crosshair,
        MouseCursorStyle::VerticalText => gpui::CursorStyle::IBeamCursorForVerticalLayout,
        MouseCursorStyle::Alias => gpui::CursorStyle::DragLink,
        MouseCursorStyle::Copy => gpui::CursorStyle::DragCopy,
        MouseCursorStyle::NoDrop => gpui::CursorStyle::OperationNotAllowed,
        MouseCursorStyle::Grab => gpui::CursorStyle::OpenHand,
        MouseCursorStyle::Grabbing => gpui::CursorStyle::ClosedHand,
        MouseCursorStyle::EwResize => gpui::CursorStyle::ResizeLeftRight,
        MouseCursorStyle::NsResize => gpui::CursorStyle::ResizeUpDown,
        MouseCursorStyle::NeswResize => gpui::CursorStyle::ResizeUpRightDownLeft,
        MouseCursorStyle::NwseResize => gpui::CursorStyle::ResizeUpLeftDownRight,
        MouseCursorStyle::ColResize => gpui::CursorStyle::ResizeColumn,
        MouseCursorStyle::RowResize => gpui::CursorStyle::ResizeRow,
        MouseCursorStyle::NResize => gpui::CursorStyle::ResizeUp,
        MouseCursorStyle::EResize => gpui::CursorStyle::ResizeRight,
        MouseCursorStyle::SResize => gpui::CursorStyle::ResizeDown,
        MouseCursorStyle::WResize => gpui::CursorStyle::ResizeLeft,
        MouseCursorStyle::None => gpui::CursorStyle::None,
    }
}

fn apply_border_width<E>(mut element: E, axis: StyleAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_absolute(length);
    let border_widths = &mut element.style().border_widths;

    match axis {
        StyleAxis::All => {
            border_widths.top = Some(length);
            border_widths.right = Some(length);
            border_widths.bottom = Some(length);
            border_widths.left = Some(length);
        }
        StyleAxis::X => {
            border_widths.left = Some(length);
            border_widths.right = Some(length);
        }
        StyleAxis::Y => {
            border_widths.top = Some(length);
            border_widths.bottom = Some(length);
        }
        StyleAxis::Top => border_widths.top = Some(length),
        StyleAxis::Right => border_widths.right = Some(length),
        StyleAxis::Bottom => border_widths.bottom = Some(length),
        StyleAxis::Left => border_widths.left = Some(length),
    }

    element
}

fn apply_border_radius<E>(mut element: E, axis: BorderRadiusAxis, length: StyleLength) -> E
where
    E: Styled,
{
    let length = style_length_to_absolute(length);
    let corner_radii = &mut element.style().corner_radii;

    match axis {
        BorderRadiusAxis::All => {
            corner_radii.top_left = Some(length);
            corner_radii.top_right = Some(length);
            corner_radii.bottom_right = Some(length);
            corner_radii.bottom_left = Some(length);
        }
        BorderRadiusAxis::Top => {
            corner_radii.top_left = Some(length);
            corner_radii.top_right = Some(length);
        }
        BorderRadiusAxis::Right => {
            corner_radii.top_right = Some(length);
            corner_radii.bottom_right = Some(length);
        }
        BorderRadiusAxis::Bottom => {
            corner_radii.bottom_left = Some(length);
            corner_radii.bottom_right = Some(length);
        }
        BorderRadiusAxis::Left => {
            corner_radii.top_left = Some(length);
            corner_radii.bottom_left = Some(length);
        }
        BorderRadiusAxis::TopLeft => corner_radii.top_left = Some(length),
        BorderRadiusAxis::TopRight => corner_radii.top_right = Some(length),
        BorderRadiusAxis::BottomLeft => corner_radii.bottom_left = Some(length),
        BorderRadiusAxis::BottomRight => corner_radii.bottom_right = Some(length),
    }

    element
}

fn apply_border_style<E>(mut element: E, border_style: BorderLineStyle) -> E
where
    E: Styled,
{
    element.style().border_style = Some(match border_style {
        BorderLineStyle::Solid => gpui::BorderStyle::Solid,
        BorderLineStyle::Dashed => gpui::BorderStyle::Dashed,
    });

    element
}

#[cfg(debug_assertions)]
fn apply_debug<E>(mut element: E, value: bool) -> E
where
    E: Styled,
{
    element.style().debug = Some(value);
    element
}

#[cfg(not(debug_assertions))]
fn apply_debug<E>(element: E, value: bool) -> E
where
    E: Styled,
{
    let _ = value;
    element
}

#[cfg(debug_assertions)]
fn apply_debug_below<E>(mut element: E, value: bool) -> E
where
    E: Styled,
{
    element.style().debug_below = Some(value);
    element
}

#[cfg(not(debug_assertions))]
fn apply_debug_below<E>(element: E, value: bool) -> E
where
    E: Styled,
{
    let _ = value;
    element
}

fn apply_shadow<E>(element: E, shadow: ShadowStyle) -> E
where
    E: Styled,
{
    match shadow {
        ShadowStyle::None => element.shadow_none(),
        ShadowStyle::TwoXs => element.shadow_2xs(),
        ShadowStyle::Xs => element.shadow_xs(),
        ShadowStyle::Sm => element.shadow_sm(),
        ShadowStyle::Md => element.shadow_md(),
        ShadowStyle::Lg => element.shadow_lg(),
        ShadowStyle::Xl => element.shadow_xl(),
        ShadowStyle::TwoXl => element.shadow_2xl(),
    }
}

fn apply_box_shadow<E>(mut element: E, shadows: &[BoxShadowSpec]) -> E
where
    E: Styled,
{
    element.style().box_shadow = Some(shadows.iter().map(box_shadow_to_gpui).collect());
    element
}

fn box_shadow_to_gpui(shadow: &BoxShadowSpec) -> BoxShadow {
    BoxShadow {
        color: style_color_to_color(&shadow.color),
        offset: point(px(shadow.x), px(shadow.y)),
        blur_radius: px(shadow.blur),
        spread_radius: px(shadow.spread),
    }
}

fn apply_flex_direction<E>(element: E, direction: FlexDirectionStyle) -> E
where
    E: Styled,
{
    match direction {
        FlexDirectionStyle::Column => element.flex_col(),
        FlexDirectionStyle::ColumnReverse => element.flex_col_reverse(),
        FlexDirectionStyle::Row => element.flex_row(),
        FlexDirectionStyle::RowReverse => element.flex_row_reverse(),
    }
}

fn apply_flex_wrap<E>(element: E, wrap: FlexWrapStyle) -> E
where
    E: Styled,
{
    match wrap {
        FlexWrapStyle::Wrap => element.flex_wrap(),
        FlexWrapStyle::WrapReverse => element.flex_wrap_reverse(),
        FlexWrapStyle::NoWrap => element.flex_nowrap(),
    }
}

fn apply_flex_item<E>(element: E, item: FlexItemStyle) -> E
where
    E: Styled,
{
    match item {
        FlexItemStyle::One => element.flex_1(),
        FlexItemStyle::Auto => element.flex_auto(),
        FlexItemStyle::Initial => element.flex_initial(),
        FlexItemStyle::None => element.flex_none(),
        FlexItemStyle::Grow => element.flex_grow(),
        FlexItemStyle::Shrink => element.flex_shrink(),
        FlexItemStyle::Shrink0 => element.flex_shrink_0(),
    }
}

fn apply_flex_grow<E>(mut element: E, value: f32) -> E
where
    E: Styled,
{
    element.style().flex_grow = Some(value);
    element
}

fn apply_flex_shrink<E>(mut element: E, value: f32) -> E
where
    E: Styled,
{
    element.style().flex_shrink = Some(value);
    element
}

fn apply_align_items<E>(mut element: E, align: AlignItemsStyle) -> E
where
    E: Styled,
{
    element.style().align_items = Some(align_items_to_gpui(align));
    element
}

fn apply_align_self<E>(mut element: E, align: AlignItemsStyle) -> E
where
    E: Styled,
{
    element.style().align_self = Some(align_items_to_gpui(align));
    element
}

fn align_items_to_gpui(align: AlignItemsStyle) -> gpui::AlignItems {
    match align {
        AlignItemsStyle::Start => gpui::AlignItems::FlexStart,
        AlignItemsStyle::End => gpui::AlignItems::FlexEnd,
        AlignItemsStyle::Center => gpui::AlignItems::Center,
        AlignItemsStyle::Baseline => gpui::AlignItems::Baseline,
        AlignItemsStyle::Stretch => gpui::AlignItems::Stretch,
    }
}

fn apply_justify_content<E>(mut element: E, justify: JustifyContentStyle) -> E
where
    E: Styled,
{
    element.style().justify_content = Some(justify_content_to_gpui(justify));
    element
}

fn justify_content_to_gpui(justify: JustifyContentStyle) -> gpui::JustifyContent {
    match justify {
        JustifyContentStyle::Start => gpui::JustifyContent::Start,
        JustifyContentStyle::End => gpui::JustifyContent::End,
        JustifyContentStyle::Center => gpui::JustifyContent::Center,
        JustifyContentStyle::Between => gpui::JustifyContent::SpaceBetween,
        JustifyContentStyle::Around => gpui::JustifyContent::SpaceAround,
        JustifyContentStyle::Evenly => gpui::JustifyContent::SpaceEvenly,
        JustifyContentStyle::Stretch => gpui::JustifyContent::Stretch,
    }
}

fn apply_align_content<E>(element: E, align: AlignContentStyle) -> E
where
    E: Styled,
{
    match align {
        AlignContentStyle::Normal => element.content_normal(),
        AlignContentStyle::Start => element.content_start(),
        AlignContentStyle::End => element.content_end(),
        AlignContentStyle::Center => element.content_center(),
        AlignContentStyle::Between => element.content_between(),
        AlignContentStyle::Around => element.content_around(),
        AlignContentStyle::Evenly => element.content_evenly(),
        AlignContentStyle::Stretch => element.content_stretch(),
    }
}

fn apply_text_align<E>(element: E, align: TextAlignStyle) -> E
where
    E: Styled,
{
    match align {
        TextAlignStyle::Left => element.text_left(),
        TextAlignStyle::Center => element.text_center(),
        TextAlignStyle::Right => element.text_right(),
    }
}

fn apply_white_space<E>(element: E, white_space: WhiteSpaceStyle) -> E
where
    E: Styled,
{
    match white_space {
        WhiteSpaceStyle::Normal => element.whitespace_normal(),
        WhiteSpaceStyle::NoWrap => element.whitespace_nowrap(),
    }
}

fn apply_text_overflow<E>(element: E, overflow: TextOverflowStyle) -> E
where
    E: Styled,
{
    match overflow {
        TextOverflowStyle::Ellipsis => element.text_ellipsis(),
        TextOverflowStyle::Truncate => element.truncate(),
    }
}

fn apply_text_size<E>(element: E, size: StyleLength) -> E
where
    E: Styled,
{
    element.text_size(style_length_to_absolute(size))
}

fn apply_font_size<E>(element: E, size: FontSizeStyle) -> E
where
    E: Styled,
{
    match size {
        FontSizeStyle::Xs => element.text_xs(),
        FontSizeStyle::Sm => element.text_sm(),
        FontSizeStyle::Base => element.text_base(),
        FontSizeStyle::Lg => element.text_lg(),
        FontSizeStyle::Xl => element.text_xl(),
        FontSizeStyle::TwoXl => element.text_2xl(),
        FontSizeStyle::ThreeXl => element.text_3xl(),
    }
}

fn apply_line_height<E>(element: E, line_height: LineHeightStyle) -> E
where
    E: Styled,
{
    element.line_height(relative(line_height_to_relative(line_height)))
}

fn line_height_to_relative(line_height: LineHeightStyle) -> f32 {
    match line_height {
        LineHeightStyle::None => 1.0,
        LineHeightStyle::Tight => 1.25,
        LineHeightStyle::Snug => 1.375,
        LineHeightStyle::Normal => 1.5,
        LineHeightStyle::Relaxed => 1.625,
        LineHeightStyle::Loose => 2.0,
    }
}

fn apply_line_height_length<E>(element: E, length: StyleLength) -> E
where
    E: Styled,
{
    element.line_height(style_length_to_definite(length))
}

fn apply_font_weight_value<E>(element: E, value: f32) -> E
where
    E: Styled,
{
    element.font_weight(FontWeight(value))
}

fn apply_font_weight<E>(element: E, weight: FontWeightStyle) -> E
where
    E: Styled,
{
    element.font_weight(font_weight_to_gpui(weight))
}

fn font_weight_to_gpui(weight: FontWeightStyle) -> FontWeight {
    match weight {
        FontWeightStyle::Thin => FontWeight::THIN,
        FontWeightStyle::Extralight => FontWeight::EXTRA_LIGHT,
        FontWeightStyle::Light => FontWeight::LIGHT,
        FontWeightStyle::Normal => FontWeight::NORMAL,
        FontWeightStyle::Medium => FontWeight::MEDIUM,
        FontWeightStyle::Semibold => FontWeight::SEMIBOLD,
        FontWeightStyle::Bold => FontWeight::BOLD,
        FontWeightStyle::Extrabold => FontWeight::EXTRA_BOLD,
        FontWeightStyle::Black => FontWeight::BLACK,
    }
}

fn apply_font_style<E>(element: E, font_style: FontStyleValue) -> E
where
    E: Styled,
{
    match font_style {
        FontStyleValue::Italic => element.italic(),
        FontStyleValue::Normal => element.not_italic(),
    }
}

fn apply_font_fallbacks<E>(mut element: E, fallbacks: &[String]) -> E
where
    E: Styled,
{
    element
        .text_style()
        .get_or_insert_with(Default::default)
        .font_fallbacks = Some(FontFallbacks::from_fonts(fallbacks.to_vec()));
    element
}

fn apply_font_features<E>(mut element: E, features: &[(String, u32)]) -> E
where
    E: Styled,
{
    element
        .text_style()
        .get_or_insert_with(Default::default)
        .font_features = Some(FontFeatures(Arc::new(features.to_vec())));
    element
}

fn apply_text_decoration<E>(element: E, decoration: TextDecorationStyle) -> E
where
    E: Styled,
{
    match decoration {
        TextDecorationStyle::Underline => element.underline(),
        TextDecorationStyle::LineThrough => element.line_through(),
        TextDecorationStyle::None => element.text_decoration_none(),
    }
}

fn apply_text_decoration_line_style<E>(element: E, line_style: TextDecorationLineStyle) -> E
where
    E: Styled,
{
    match line_style {
        TextDecorationLineStyle::Solid => element.text_decoration_solid(),
        TextDecorationLineStyle::Wavy => element.text_decoration_wavy(),
    }
}

fn apply_text_decoration_thickness<E>(mut element: E, thickness: f32) -> E
where
    E: Styled,
{
    element
        .style()
        .text
        .get_or_insert_with(Default::default)
        .underline
        .get_or_insert_with(Default::default)
        .thickness = px(thickness);
    element
}

fn apply_strikethrough_color<E>(element: E, color: ColorToken) -> E
where
    E: Styled,
{
    apply_strikethrough_color_value(element, color_token_to_color(color))
}

fn apply_strikethrough_color_value<E>(mut element: E, color: gpui::Hsla) -> E
where
    E: Styled,
{
    element
        .style()
        .text
        .get_or_insert_with(Default::default)
        .strikethrough
        .get_or_insert_with(Default::default)
        .color = Some(color);
    element
}

fn apply_strikethrough_thickness<E>(mut element: E, thickness: f32) -> E
where
    E: Styled,
{
    element
        .style()
        .text
        .get_or_insert_with(Default::default)
        .strikethrough
        .get_or_insert_with(Default::default)
        .thickness = px(thickness);
    element
}

fn style_length_to_definite(length: StyleLength) -> DefiniteLength {
    match length {
        StyleLength::Px(value) => px(value).into(),
        StyleLength::Rem(value) => rems(value).into(),
        StyleLength::Fraction(value) => relative(value),
        StyleLength::Auto => px(0.0).into(),
    }
}

fn style_length_to_absolute(length: StyleLength) -> gpui::AbsoluteLength {
    match length {
        StyleLength::Px(value) => px(value).into(),
        StyleLength::Rem(value) => rems(value).into(),
        StyleLength::Fraction(_) | StyleLength::Auto => px(0.0).into(),
    }
}

fn style_length_to_length(length: StyleLength) -> Length {
    match length {
        StyleLength::Px(value) => px(value).into(),
        StyleLength::Rem(value) => rems(value).into(),
        StyleLength::Fraction(value) => relative(value).into(),
        StyleLength::Auto => Length::Auto,
    }
}

pub(crate) fn style_ops_to_highlight_style(ops: &DivStyle) -> HighlightStyle {
    let mut highlight = HighlightStyle::default();

    for op in ops.iter() {
        if let Some(color) = highlight_text_color(op) {
            highlight.color = Some(color);
            continue;
        }

        if let Some(color) = highlight_background_color(op) {
            highlight.background_color = Some(color);
            continue;
        }

        match op {
            StyleOp::FontThin => highlight.font_weight = Some(FontWeight::THIN),
            StyleOp::FontExtralight => highlight.font_weight = Some(FontWeight::EXTRA_LIGHT),
            StyleOp::FontLight => highlight.font_weight = Some(FontWeight::LIGHT),
            StyleOp::FontNormal => highlight.font_weight = Some(FontWeight::NORMAL),
            StyleOp::FontMedium => highlight.font_weight = Some(FontWeight::MEDIUM),
            StyleOp::FontSemibold => highlight.font_weight = Some(FontWeight::SEMIBOLD),
            StyleOp::FontBold => highlight.font_weight = Some(FontWeight::BOLD),
            StyleOp::FontExtrabold => highlight.font_weight = Some(FontWeight::EXTRA_BOLD),
            StyleOp::FontBlack => highlight.font_weight = Some(FontWeight::BLACK),
            StyleOp::FontWeight(weight) => {
                highlight.font_weight = Some(font_weight_to_gpui(*weight))
            }
            StyleOp::FontWeightValue(value) => highlight.font_weight = Some(FontWeight(*value)),
            StyleOp::Italic => highlight.font_style = Some(FontStyle::Italic),
            StyleOp::NotItalic => highlight.font_style = Some(FontStyle::Normal),
            StyleOp::FontStyle(FontStyleValue::Italic) => {
                highlight.font_style = Some(FontStyle::Italic)
            }
            StyleOp::FontStyle(FontStyleValue::Normal) => {
                highlight.font_style = Some(FontStyle::Normal)
            }
            StyleOp::Underline => {
                highlight.underline = Some(UnderlineStyle {
                    thickness: px(1.0),
                    color: None,
                    wavy: false,
                });
            }
            StyleOp::LineThrough => {
                highlight.strikethrough = Some(StrikethroughStyle {
                    thickness: px(1.0),
                    color: None,
                });
            }
            StyleOp::TextDecoration(TextDecorationStyle::Underline) => {
                highlight.underline = Some(UnderlineStyle {
                    thickness: px(1.0),
                    color: None,
                    wavy: false,
                });
            }
            StyleOp::TextDecoration(TextDecorationStyle::LineThrough) => {
                highlight.strikethrough = Some(StrikethroughStyle {
                    thickness: px(1.0),
                    color: None,
                });
            }
            StyleOp::TextDecoration(TextDecorationStyle::None) => {
                highlight.underline = None;
                highlight.strikethrough = None;
            }
            StyleOp::TextDecorationColor(_) | StyleOp::TextDecorationColorHex(_) => {
                highlight
                    .underline
                    .get_or_insert_with(Default::default)
                    .color = highlight_decoration_color(op);
            }
            StyleOp::TextDecorationLineStyle(line_style) => {
                highlight
                    .underline
                    .get_or_insert_with(Default::default)
                    .wavy = *line_style == TextDecorationLineStyle::Wavy;
            }
            StyleOp::TextDecorationThickness(value) => {
                highlight
                    .underline
                    .get_or_insert_with(Default::default)
                    .thickness = px(*value);
            }
            StyleOp::StrikethroughColor(_) | StyleOp::StrikethroughColorHex(_) => {
                highlight
                    .strikethrough
                    .get_or_insert_with(Default::default)
                    .color = highlight_strikethrough_color(op);
            }
            StyleOp::StrikethroughThickness(value) => {
                highlight
                    .strikethrough
                    .get_or_insert_with(Default::default)
                    .thickness = px(*value);
            }
            StyleOp::Opacity(value) => highlight.fade_out = Some(1.0 - value.clamp(0.0, 1.0)),
            _ => {}
        }
    }

    highlight
}

fn highlight_text_color(op: &StyleOp) -> Option<gpui::Hsla> {
    match op {
        StyleOp::TextColor(color) => Some(color_token_to_color(*color)),
        StyleOp::TextColorHex(value) => Some(hex_color_to_color(*value)),
        _ => None,
    }
}

fn highlight_background_color(op: &StyleOp) -> Option<gpui::Hsla> {
    match op {
        StyleOp::Bg(color) | StyleOp::TextBg(color) => Some(color_token_to_color(*color)),
        StyleOp::BgHex(value) | StyleOp::TextBgHex(value) => Some(hex_color_to_color(*value)),
        _ => None,
    }
}

fn highlight_decoration_color(op: &StyleOp) -> Option<gpui::Hsla> {
    match op {
        StyleOp::TextDecorationColor(color) => Some(color_token_to_color(*color)),
        StyleOp::TextDecorationColorHex(value) => Some(hex_color_to_color(*value)),
        _ => None,
    }
}

fn highlight_strikethrough_color(op: &StyleOp) -> Option<gpui::Hsla> {
    match op {
        StyleOp::StrikethroughColor(color) => Some(color_token_to_color(*color)),
        StyleOp::StrikethroughColorHex(value) => Some(hex_color_to_color(*value)),
        _ => None,
    }
}

pub(crate) fn apply_refinement_style(
    mut style: StyleRefinement,
    ops: &DivStyle,
) -> StyleRefinement {
    for op in ops.iter() {
        style = match apply_refinement_supported_style_op(style, op) {
            StyleApplication::Applied(style) | StyleApplication::Unsupported(style) => style,
        };
    }

    style
}

fn color_token_to_color(color: ColorToken) -> gpui::Hsla {
    match color {
        ColorToken::Red => rgb(0xff0000).into(),
        ColorToken::Green => rgb(0x00ff00).into(),
        ColorToken::Blue => rgb(0x0000ff).into(),
        ColorToken::Yellow => rgb(0xffff00).into(),
        ColorToken::Black => rgb(0x000000).into(),
        ColorToken::White => rgb(0xffffff).into(),
        ColorToken::Gray => rgb(0x505050).into(),
    }
}

pub(crate) fn style_color_to_color(color: &StyleColor) -> gpui::Hsla {
    match color {
        StyleColor::Token(color) => color_token_to_color(*color),
        StyleColor::Hex(value) => hex_color_to_color(*value),
    }
}

fn linear_gradient_stop_to_gpui(stop: &LinearGradientStop) -> gpui::LinearColorStop {
    linear_color_stop(style_color_to_color(&stop.color), stop.percentage)
}

fn hex_color_to_color(value: u32) -> gpui::Hsla {
    rgb(value).into()
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::{BackgroundPatternSlash, BoxShadowSpec, LinearGradientStop, StyleColor};
    use gpui::{
        BoxShadow, Fill, InteractiveElement, SharedString, StyleRefinement, div, linear_color_stop,
        linear_gradient, pattern_slash, point,
    };

    #[test]
    fn semantic_focus_visible_affordance_applies_default_outline() {
        let mut element = apply_semantic_focus_visible_affordance(
            div().id(SharedString::from("focused_semantic_row")),
            true,
        );
        let style = element.style();
        assert_eq!(style.border_widths.top, Some(px(1.0).into()));
        assert_eq!(style.border_widths.right, Some(px(1.0).into()));
        assert_eq!(style.border_widths.bottom, Some(px(1.0).into()));
        assert_eq!(style.border_widths.left, Some(px(1.0).into()));
        assert_eq!(style.border_color, Some(rgb(0x60a5fa).into()));

        let mut element = apply_semantic_focus_visible_affordance(
            div().id(SharedString::from("mouse_focused_semantic_row")),
            false,
        );
        let style = element.style();
        assert_eq!(style.border_widths.top, None);
        assert_eq!(style.border_color, None);
    }

    #[test]
    fn applies_canonical_box_spacing_to_style_refinement() {
        let style = apply_padding(
            StyleRefinement::default(),
            StyleAxis::Y,
            StyleLength::Rem(0.25),
        );

        assert_eq!(style.padding.top, Some(DefiniteLength::from(rems(0.25))));
        assert_eq!(style.padding.bottom, Some(DefiniteLength::from(rems(0.25))));
        assert_eq!(style.padding.left, None);
        assert_eq!(style.padding.right, None);

        let style = apply_margin(StyleRefinement::default(), StyleAxis::X, StyleLength::Auto);
        assert_eq!(style.margin.left, Some(Length::Auto));
        assert_eq!(style.margin.right, Some(Length::Auto));
        assert_eq!(style.margin.top, None);
        assert_eq!(style.margin.bottom, None);

        let style = apply_gap(
            StyleRefinement::default(),
            StyleAxis::All,
            StyleLength::Px(-1.0),
        );
        assert_eq!(style.gap.width, Some(DefiniteLength::from(px(-1.0))));
        assert_eq!(style.gap.height, Some(DefiniteLength::from(px(-1.0))));

        let style = apply_length_style(
            StyleRefinement::default(),
            LengthStyleProperty::Width,
            StyleLength::Fraction(1.0),
        );
        assert_eq!(style.size.width, Some(Length::Definite(relative(1.0))));

        let style = apply_length_style(
            StyleRefinement::default(),
            LengthStyleProperty::Height,
            StyleLength::Auto,
        );
        assert_eq!(style.size.height, Some(Length::Auto));

        let style = apply_aspect_ratio(StyleRefinement::default(), 1.5);
        assert_eq!(style.aspect_ratio, Some(1.5));

        let style = apply_position(StyleRefinement::default(), PositionStyle::Absolute);
        assert_eq!(style.position, Some(gpui::Position::Absolute));

        let style = apply_inset(
            StyleRefinement::default(),
            StyleAxis::Top,
            StyleLength::Rem(-0.5),
        );
        assert_eq!(style.inset.top, Some(Length::Definite(rems(-0.5).into())));

        let style = apply_display(StyleRefinement::default(), DisplayStyle::Flex);
        assert_eq!(style.display, Some(gpui::Display::Flex));

        let style = apply_visibility(StyleRefinement::default(), VisibilityStyle::Hidden);
        assert_eq!(style.visibility, Some(gpui::Visibility::Hidden));

        let style = apply_overflow(
            StyleRefinement::default(),
            StyleAxis::X,
            OverflowStyle::Scroll,
        );
        assert_eq!(style.overflow.x, Some(gpui::Overflow::Scroll));

        let style = apply_overflow(
            StyleRefinement::default(),
            StyleAxis::Y,
            OverflowStyle::Clip,
        );
        assert_eq!(style.overflow.y, Some(gpui::Overflow::Clip));

        let style = apply_overflow(
            StyleRefinement::default(),
            StyleAxis::All,
            OverflowStyle::Visible,
        );
        assert_eq!(style.overflow.x, Some(gpui::Overflow::Visible));
        assert_eq!(style.overflow.y, Some(gpui::Overflow::Visible));

        let style = apply_allow_concurrent_scroll(StyleRefinement::default(), true);
        assert_eq!(style.allow_concurrent_scroll, Some(true));

        let style = apply_restrict_scroll_to_axis(StyleRefinement::default(), true);
        assert_eq!(style.restrict_scroll_to_axis, Some(true));

        let style = apply_cursor(StyleRefinement::default(), MouseCursorStyle::NotAllowed);
        assert_eq!(
            style.mouse_cursor,
            Some(gpui::CursorStyle::OperationNotAllowed)
        );

        let style = apply_border_width(
            StyleRefinement::default(),
            StyleAxis::X,
            StyleLength::Px(4.0),
        );
        assert_eq!(style.border_widths.left, Some(px(4.0).into()));
        assert_eq!(style.border_widths.right, Some(px(4.0).into()));
        assert_eq!(style.border_widths.top, None);

        let style = apply_border_radius(
            StyleRefinement::default(),
            BorderRadiusAxis::BottomRight,
            StyleLength::Rem(0.25),
        );
        assert_eq!(style.corner_radii.bottom_right, Some(rems(0.25).into()));
        assert_eq!(style.corner_radii.top_left, None);

        let style = apply_border_style(StyleRefinement::default(), BorderLineStyle::Dashed);
        assert_eq!(style.border_style, Some(gpui::BorderStyle::Dashed));

        let style = apply_shadow(StyleRefinement::default(), ShadowStyle::None);
        assert_eq!(style.box_shadow, Some(vec![]));

        let style = apply_box_shadow(
            StyleRefinement::default(),
            &[BoxShadowSpec {
                color: StyleColor::Token(ColorToken::Red),
                x: 0.0,
                y: 2.0,
                blur: 4.0,
                spread: -1.0,
            }],
        );
        assert_eq!(
            style.box_shadow,
            Some(vec![BoxShadow {
                color: color_token_to_color(ColorToken::Red),
                offset: point(px(0.0), px(2.0)),
                blur_radius: px(4.0),
                spread_radius: px(-1.0),
            }])
        );

        #[cfg(debug_assertions)]
        {
            let style = apply_debug_below(apply_debug(StyleRefinement::default(), true), true);
            assert_eq!(style.debug, Some(true));
            assert_eq!(style.debug_below, Some(true));
        }

        let style =
            apply_flex_direction(StyleRefinement::default(), FlexDirectionStyle::RowReverse);
        assert_eq!(style.flex_direction, Some(gpui::FlexDirection::RowReverse));

        let style = apply_flex_wrap(StyleRefinement::default(), FlexWrapStyle::NoWrap);
        assert_eq!(style.flex_wrap, Some(gpui::FlexWrap::NoWrap));

        let style = apply_flex_item(StyleRefinement::default(), FlexItemStyle::Shrink0);
        assert_eq!(style.flex_shrink, Some(0.0));

        let style = apply_flex_grow(StyleRefinement::default(), 2.0);
        assert_eq!(style.flex_grow, Some(2.0));

        let style = apply_flex_shrink(StyleRefinement::default(), 0.5);
        assert_eq!(style.flex_shrink, Some(0.5));

        let style = apply_align_items(StyleRefinement::default(), AlignItemsStyle::Baseline);
        assert_eq!(style.align_items, Some(gpui::AlignItems::Baseline));

        let style = apply_align_self(StyleRefinement::default(), AlignItemsStyle::Stretch);
        assert_eq!(style.align_self, Some(gpui::AlignItems::Stretch));

        let style = apply_justify_content(StyleRefinement::default(), JustifyContentStyle::Around);
        assert_eq!(
            style.justify_content,
            Some(gpui::JustifyContent::SpaceAround)
        );

        let style = apply_justify_content(StyleRefinement::default(), JustifyContentStyle::Evenly);
        assert_eq!(
            style.justify_content,
            Some(gpui::JustifyContent::SpaceEvenly)
        );

        let style = apply_align_content(StyleRefinement::default(), AlignContentStyle::Evenly);
        assert_eq!(style.align_content, Some(gpui::AlignContent::SpaceEvenly));

        let style = apply_text_align(StyleRefinement::default(), TextAlignStyle::Center);
        assert_eq!(
            style.text.as_ref().and_then(|text| text.text_align),
            Some(gpui::TextAlign::Center)
        );

        let style = apply_text_size(StyleRefinement::default(), StyleLength::Px(14.0));
        assert_eq!(
            style.text.as_ref().and_then(|text| text.font_size),
            Some(px(14.0).into())
        );

        let style =
            apply_line_height_length(StyleRefinement::default(), StyleLength::Fraction(1.4));
        assert_eq!(
            style.text.as_ref().and_then(|text| text.line_height),
            Some(relative(1.4))
        );

        let style = apply_font_weight(StyleRefinement::default(), FontWeightStyle::Bold);
        assert_eq!(
            style.text.as_ref().and_then(|text| text.font_weight),
            Some(FontWeight::BOLD)
        );

        let style = apply_font_weight_value(StyleRefinement::default(), 650.0);
        assert_eq!(
            style.text.as_ref().and_then(|text| text.font_weight),
            Some(FontWeight(650.0))
        );

        let style = apply_font_fallbacks(
            StyleRefinement::default(),
            &["Monaco".to_string(), "Menlo".to_string()],
        );
        assert_eq!(
            style
                .text
                .and_then(|text| text.font_fallbacks)
                .map(|fallbacks| fallbacks.fallback_list().to_vec()),
            Some(vec!["Monaco".to_string(), "Menlo".to_string()])
        );

        let style = apply_font_features(
            StyleRefinement::default(),
            &[("calt".to_string(), 0), ("kern".to_string(), 1)],
        );
        assert_eq!(
            style
                .text
                .and_then(|text| text.font_features)
                .map(|features| features.tag_value_list().to_vec()),
            Some(vec![("calt".to_string(), 0), ("kern".to_string(), 1)])
        );

        let style = apply_text_decoration(StyleRefinement::default(), TextDecorationStyle::None);
        assert_eq!(style.text.as_ref().and_then(|text| text.underline), None);
        assert_eq!(
            style.text.as_ref().and_then(|text| text.strikethrough),
            None
        );

        let style = apply_strikethrough_thickness(
            apply_strikethrough_color(StyleRefinement::default(), ColorToken::Red),
            2.0,
        );
        let strikethrough = style
            .text
            .as_ref()
            .and_then(|text| text.strikethrough)
            .unwrap();
        assert_eq!(
            strikethrough.color,
            Some(color_token_to_color(ColorToken::Red))
        );
        assert_eq!(strikethrough.thickness, px(2.0));

        let style = apply_refinement_style(
            StyleRefinement::default(),
            &vec![
                StyleOp::TextDecoration(TextDecorationStyle::Underline),
                StyleOp::TextDecorationColor(ColorToken::Red),
                StyleOp::TextDecorationLineStyle(TextDecorationLineStyle::Wavy),
                StyleOp::TextDecorationThickness(2.0),
                StyleOp::TextDecoration(TextDecorationStyle::LineThrough),
                StyleOp::StrikethroughColor(ColorToken::Blue),
                StyleOp::StrikethroughThickness(3.0),
            ]
            .into(),
        );
        let underline = style.text.as_ref().and_then(|text| text.underline).unwrap();
        let strikethrough = style
            .text
            .as_ref()
            .and_then(|text| text.strikethrough)
            .unwrap();
        assert_eq!(underline.color, Some(rgb(0xff0000).into()));
        assert_eq!(underline.thickness, px(2.0));
        assert!(underline.wavy);
        assert_eq!(
            strikethrough.color,
            Some(color_token_to_color(ColorToken::Blue))
        );
        assert_eq!(strikethrough.thickness, px(3.0));
    }

    #[test]
    fn applies_bg_linear_gradient_to_refinement_background() {
        let ops = vec![
            StyleOp::Bg(ColorToken::Gray),
            StyleOp::BgLinearGradient {
                angle: 90.0,
                from: LinearGradientStop {
                    color: StyleColor::Hex(0x0f172a),
                    percentage: 0.0,
                },
                to: LinearGradientStop {
                    color: StyleColor::Hex(0x2563eb),
                    percentage: 1.0,
                },
            },
        ]
        .into();

        let style = apply_refinement_style(StyleRefinement::default(), &ops);

        assert_eq!(
            style.background,
            Some(Fill::from(linear_gradient(
                90.0,
                linear_color_stop(rgb(0x0f172a), 0.0),
                linear_color_stop(rgb(0x2563eb), 1.0),
            )))
        );

        let style = apply_refinement_style(
            StyleRefinement::default(),
            &vec![StyleOp::BgPatternSlash(BackgroundPatternSlash {
                color: StyleColor::Token(ColorToken::Red),
                width: 1.0,
                interval: 4.0,
            })]
            .into(),
        );

        assert_eq!(
            style.background,
            Some(Fill::from(pattern_slash(
                color_token_to_color(ColorToken::Red),
                1.0,
                4.0,
            )))
        );
    }

    #[test]
    fn refinement_style_ignores_documented_layout_and_interactive_ops() {
        let ops = vec![
            StyleOp::Flex,
            StyleOp::GridCols(3),
            StyleOp::ColStart(2),
            StyleOp::OverflowHidden,
            StyleOp::ScrollbarWidthPx(12.0),
            StyleOp::TextColor(ColorToken::Red),
        ]
        .into();

        let style = apply_refinement_style(StyleRefinement::default(), &ops);

        assert_eq!(style.display, None);
        assert_eq!(style.grid_cols, None);
        assert_eq!(
            style.text.as_ref().and_then(|text| text.color),
            Some(rgb(0xff0000).into())
        );
    }
}