use crate::layout::Display;
use crate::style::{
Color, ComputedStyle, ImportantMask, Modifier, Rule, RuleOrigin, TuiColor, TuiStyle, Value,
resolve_tui_color,
};
pub(super) fn apply_cascade_ladder(
working: &mut ComputedStyle,
sorted_by_spec: &[&Rule],
inline: Option<&TuiStyle>,
parent: &ComputedStyle,
) {
apply_custom_properties(working, sorted_by_spec, inline);
let initial = Initials::default();
for rule in sorted_by_spec {
if rule.origin == RuleOrigin::UserAgent {
apply_style(
working,
&rule.style,
parent,
false,
&initial,
);
}
}
for rule in sorted_by_spec {
if rule.origin == RuleOrigin::Author {
apply_style(working, &rule.style, parent, false, &initial);
}
}
if let Some(s) = inline {
apply_style(working, s, parent, false, &initial);
}
if let Some(s) = inline {
apply_style(working, s, parent, true, &initial);
}
for rule in sorted_by_spec {
if rule.origin == RuleOrigin::Author {
apply_style(working, &rule.style, parent, true, &initial);
}
}
for rule in sorted_by_spec {
if rule.origin == RuleOrigin::UserAgent {
apply_style(working, &rule.style, parent, true, &initial);
}
}
}
fn apply_custom_properties(
working: &mut ComputedStyle,
sorted_by_spec: &[&Rule],
inline: Option<&TuiStyle>,
) {
let declares = sorted_by_spec
.iter()
.any(|r| !r.style.custom_properties.is_empty())
|| inline.is_some_and(|s| !s.custom_properties.is_empty());
if !declares {
return;
}
let inherited = working.vars.clone();
let map = std::rc::Rc::make_mut(&mut working.vars);
let mut put = |style: &TuiStyle, important_pass: bool| {
for d in &style.custom_properties {
if d.important != important_pass {
continue;
}
match d.value.trim() {
v if v.eq_ignore_ascii_case("initial") => {
map.remove(&d.name);
}
v if v.eq_ignore_ascii_case("inherit") || v.eq_ignore_ascii_case("unset") => {
match inherited.get(&d.name) {
Some(parent_value) => {
map.insert(d.name.clone(), parent_value.clone());
}
None => {
map.remove(&d.name);
}
}
}
_ => {
map.insert(d.name.clone(), d.value.clone());
}
}
}
};
for origin in [RuleOrigin::UserAgent, RuleOrigin::Author] {
for rule in sorted_by_spec.iter().filter(|r| r.origin == origin) {
put(&rule.style, false);
}
}
if let Some(s) = inline {
put(s, false);
put(s, true);
}
for origin in [RuleOrigin::Author, RuleOrigin::UserAgent] {
for rule in sorted_by_spec.iter().filter(|r| r.origin == origin) {
put(&rule.style, true);
}
}
}
pub(super) fn finalize_bfc_formation(working: &mut ComputedStyle) {
use crate::layout::{Flow, Overflow, Position};
working.establishes_new_bfc = matches!(working.flow, Flow::Flex)
|| matches!(working.display, Display::InlineBlock)
|| !matches!(working.overflow_x, Overflow::Visible)
|| !matches!(working.overflow_y, Overflow::Visible)
|| matches!(working.position, Position::Absolute | Position::Fixed);
}
pub(super) fn finalize_border_fg(
working: &mut ComputedStyle,
sorted_by_spec: &[&Rule],
inline: Option<&TuiStyle>,
) {
let declared_in_rules = sorted_by_spec.iter().any(|r| r.style.border_fg.is_some());
let declared_inline = inline.is_some_and(|s| s.border_fg.is_some());
if !declared_in_rules && !declared_inline {
working.border_fg = working.fg;
}
}
fn apply_style(
working: &mut ComputedStyle,
style: &TuiStyle,
parent: &ComputedStyle,
important_pass: bool,
initial: &Initials,
) {
let vars = working.vars.clone();
macro_rules! value {
($($field:ident: $mask:ident),* $(,)?) => {$(
apply_value(
&mut working.$field,
&style.$field,
style.important.contains(ImportantMask::$mask),
important_pass,
&parent.$field,
initial,
|c| &c.$field,
);
)*};
}
macro_rules! optional {
($($field:ident: $mask:ident),* $(,)?) => {$(
apply_optional(
&mut working.$field,
&style.$field,
style.important.contains(ImportantMask::$mask),
important_pass,
parent.$field,
initial,
|c| c.$field,
);
)*};
}
apply_color(
&mut working.fg,
&style.fg,
style.important.contains(ImportantMask::FG),
important_pass,
parent.fg,
|| initial.get().fg,
&vars,
);
apply_color(
&mut working.bg,
&style.bg,
style.important.contains(ImportantMask::BG),
important_pass,
parent.bg,
|| initial.get().bg,
&vars,
);
let current_color = working.fg;
apply_color(
&mut working.border_fg,
&style.border_fg,
style.important.contains(ImportantMask::BORDER_FG),
important_pass,
parent.border_fg,
|| current_color,
&vars,
);
apply_modifier_bit(
working,
Modifier::BOLD,
&style.bold,
style.important.contains(ImportantMask::BOLD),
important_pass,
parent.modifiers,
initial,
);
apply_modifier_bit(
working,
Modifier::ITALIC,
&style.italic,
style.important.contains(ImportantMask::ITALIC),
important_pass,
parent.modifiers,
initial,
);
apply_text_decoration(
working,
&style.text_decoration,
style.important.contains(ImportantMask::TEXT_DECORATION),
important_pass,
parent.modifiers,
initial,
);
apply_opacity(
working,
&style.opacity,
style.important.contains(ImportantMask::OPACITY),
important_pass,
parent.opacity,
initial,
);
value!(width: WIDTH, height: HEIGHT);
optional!(
min_width: MIN_WIDTH,
max_width: MAX_WIDTH,
min_height: MIN_HEIGHT,
max_height: MAX_HEIGHT,
aspect_ratio: ASPECT_RATIO,
);
value!(
padding: PADDING,
margin: MARGIN,
gap: GAP,
flex_shrink: FLEX_SHRINK,
border: BORDER,
);
apply_border_collapse(
&mut working.border_collapse,
&mut working.border_collapse_declared,
&style.border_collapse,
style.important.contains(ImportantMask::BORDER_COLLAPSE),
important_pass,
parent.border_collapse,
initial,
);
value!(
direction: DIRECTION,
overflow_x: OVERFLOW_X,
overflow_y: OVERFLOW_Y,
scrollbar_gutter: SCROLLBAR_GUTTER,
scroll_behavior: SCROLL_BEHAVIOR,
display: DISPLAY,
flow: FLOW,
white_space: WHITE_SPACE,
user_select: USER_SELECT,
pointer_events: POINTER_EVENTS,
caret_color: CARET_COLOR,
caret_text_color: CARET_TEXT_COLOR,
);
value!(
position: POSITION,
top: TOP,
right: RIGHT,
bottom: BOTTOM,
left: LEFT,
z_index: Z_INDEX,
transition_property: TRANSITIONS,
transition_duration: TRANSITIONS,
transition_timing_function: TRANSITIONS,
transition_delay: TRANSITIONS,
counter_reset: COUNTER_RESET,
counter_increment: COUNTER_INCREMENT,
);
}
#[inline]
fn matches_pass(important_prop: bool, important_pass: bool) -> bool {
important_prop == important_pass
}
#[derive(Default)]
pub(super) struct Initials(std::cell::OnceCell<ComputedStyle>);
impl Initials {
fn get(&self) -> &ComputedStyle {
self.0.get_or_init(ComputedStyle::initial)
}
}
fn apply_value<T: Clone>(
target: &mut T,
value: &Option<Value<T>>,
important_prop: bool,
important_pass: bool,
inherit: &T,
initial: &Initials,
field: fn(&ComputedStyle) -> &T,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
*target = match v {
Value::Specified(x) => x.clone(),
Value::Inherit => inherit.clone(),
Value::Initial => field(initial.get()).clone(),
};
}
}
fn apply_optional<T: Copy>(
target: &mut Option<T>,
value: &Option<Value<T>>,
important_prop: bool,
important_pass: bool,
inherit: Option<T>,
initial: &Initials,
field: fn(&ComputedStyle) -> Option<T>,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
*target = match v {
Value::Specified(x) => Some(*x),
Value::Inherit => inherit,
Value::Initial => field(initial.get()),
};
}
}
fn apply_color(
target: &mut Color,
value: &Option<Value<TuiColor>>,
important_prop: bool,
important_pass: bool,
inherit: Color,
initial: impl FnOnce() -> Color,
vars: &std::collections::HashMap<String, String>,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
*target = match v {
Value::Specified(tc) => resolve_tui_color(tc, vars, inherit),
Value::Inherit => inherit,
Value::Initial => initial(),
};
}
}
fn apply_border_collapse(
target: &mut crate::layout::BorderCollapse,
declared: &mut bool,
value: &Option<Value<crate::layout::BorderCollapse>>,
important_prop: bool,
important_pass: bool,
inherit: crate::layout::BorderCollapse,
initial: &Initials,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
match v {
Value::Specified(x) => {
*target = *x;
*declared = true;
}
Value::Inherit => {
*target = inherit;
}
Value::Initial => {
*target = initial.get().border_collapse;
*declared = true;
}
}
}
}
fn apply_opacity(
working: &mut ComputedStyle,
value: &Option<Value<f32>>,
important_prop: bool,
important_pass: bool,
inherit: f32,
initial: &Initials,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
working.opacity = match v {
Value::Specified(v) => v.clamp(0.0, 1.0),
Value::Inherit => inherit,
Value::Initial => initial.get().opacity,
};
}
}
fn decoration_of(modifiers: Modifier) -> crate::layout::TextDecoration {
use crate::layout::TextDecoration;
if modifiers.contains(Modifier::UNDERLINED) {
TextDecoration::Underline
} else if modifiers.contains(Modifier::CROSSED_OUT) {
TextDecoration::LineThrough
} else {
TextDecoration::None
}
}
fn apply_text_decoration(
working: &mut ComputedStyle,
value: &Option<Value<crate::layout::TextDecoration>>,
important_prop: bool,
important_pass: bool,
parent_modifiers: Modifier,
initial: &Initials,
) {
use crate::layout::TextDecoration;
let Some(v) = value else { return };
if !matches_pass(important_prop, important_pass) {
return;
}
let resolved = match v {
Value::Specified(v) => *v,
Value::Inherit => decoration_of(parent_modifiers),
Value::Initial => decoration_of(initial.get().modifiers),
};
working
.modifiers
.remove(Modifier::UNDERLINED | Modifier::CROSSED_OUT);
match resolved {
TextDecoration::None => {}
TextDecoration::Underline => working.modifiers.insert(Modifier::UNDERLINED),
TextDecoration::LineThrough => working.modifiers.insert(Modifier::CROSSED_OUT),
}
}
fn apply_modifier_bit(
working: &mut ComputedStyle,
bit: Modifier,
value: &Option<Value<bool>>,
important_prop: bool,
important_pass: bool,
parent_modifiers: Modifier,
initial: &Initials,
) {
if let Some(v) = value
&& matches_pass(important_prop, important_pass)
{
let on = match v {
Value::Specified(b) => *b,
Value::Inherit => parent_modifiers.contains(bit),
Value::Initial => initial.get().modifiers.contains(bit),
};
working.modifiers.set(bit, on);
}
}