use rich_rs::{MetaValue, Segments};
use crate::node_id::{NodeId, node_id_to_ffi};
use crate::renderables::{TextOpacity, Tint};
use crate::style::Style;
pub(crate) fn apply_style_to_segments(
widget_id: NodeId,
segments: Segments,
style: Style,
parent_style: Option<Style>,
) -> Segments {
if style.is_empty() {
return segments;
}
let rich_attrs = style.to_rich_without_colors();
let fallback_bg = crate::style::parse_color_like("$background");
let parent_bg = crate::css::current_composited_background()
.or_else(|| parent_style.and_then(|s| s.bg))
.or(fallback_bg);
segments
.into_iter()
.map(|mut seg| {
if seg.control.is_some() {
return seg;
}
if let Some(meta) = seg.meta.as_ref().and_then(|meta| meta.meta.as_ref()) {
if let Some(MetaValue::Int(value)) = meta.get("textual:widget_id") {
if *value != node_id_to_ffi(widget_id) as i64 {
return seg;
}
}
}
let mut no_text_style = false;
if let Some(meta) = seg.meta.as_ref().and_then(|meta| meta.meta.as_ref()) {
if let Some(MetaValue::Bool(true)) = meta.get("textual:no_style") {
return seg;
}
if let Some(MetaValue::Bool(true)) = meta.get("textual:no_text_style") {
no_text_style = true;
}
if let Some(MetaValue::Bool(true)) = meta.get("textual:no_bg") {
}
}
if !no_text_style {
if let Some(rich_attrs) = rich_attrs {
seg.style = Some(match seg.style {
Some(existing) => rich_attrs.combine(&existing),
None => rich_attrs,
});
}
}
let mut no_bg = false;
if let Some(meta) = seg.meta.as_ref().and_then(|meta| meta.meta.as_ref()) {
if let Some(MetaValue::Bool(true)) = meta.get("textual:no_bg") {
no_bg = true;
}
}
let mut style_changed = false;
let mut s = seg.style.unwrap_or_else(rich_rs::Style::new);
let explicit_bg = s.bgcolor.and_then(|bg| {
if matches!(bg, rich_rs::SimpleColor::Default) {
None
} else {
Some(bg)
}
});
let mut under_bg = explicit_bg
.map(crate::style::color_from_simple)
.or(parent_bg)
.unwrap_or(crate::style::Color::rgb(0, 0, 0));
if !no_bg {
if explicit_bg.is_none() {
let effective_bg = if let Some(bg) = style.bg {
bg.flatten_over(under_bg)
} else {
under_bg
};
under_bg = effective_bg;
s.bgcolor = Some(effective_bg.to_simple_opaque());
style_changed = true;
}
} else if s.bgcolor.is_some() {
s.bgcolor = None;
style_changed = true;
}
if let Some(tint) = style.background_tint {
if let Some(bg) = s.bgcolor {
let bg = crate::style::color_from_simple(bg);
let blended =
Tint::<()>::blend_color_with_percent(bg, tint.color, tint.percent);
let flat = blended.flatten_over(under_bg);
under_bg = flat;
s.bgcolor = Some(flat.to_simple_opaque());
style_changed = true;
}
}
let text_opacity = style.text_opacity.map(|value| value as f32 / 100.0);
if s.color.is_none() {
let bg_for_text = s
.bgcolor
.map(crate::style::color_from_simple)
.unwrap_or(under_bg);
if let Some(fg) = style.fg {
let mut fg = fg;
if let Some(opacity) = text_opacity {
fg = TextOpacity::<()>::apply_alpha(fg, opacity);
}
let flat = fg.flatten_over(bg_for_text);
s.color = Some(flat.to_simple_opaque());
style_changed = true;
} else if let Some(auto) = style.fg_auto {
let auto_alpha = auto.alpha();
let effective_alpha = if let Some(opacity) = text_opacity {
(auto_alpha * opacity).clamp(0.0, 1.0)
} else {
auto_alpha
};
let contrast =
crate::style::contrast_text(bg_for_text).with_alpha(effective_alpha);
let flat = contrast.flatten_over(bg_for_text);
s.color = Some(flat.to_simple_opaque());
style_changed = true;
}
} else if let (Some(opacity), Some(existing)) = (text_opacity, s.color) {
let bg_for_text = s
.bgcolor
.map(crate::style::color_from_simple)
.unwrap_or(under_bg);
let existing = crate::style::color_from_simple(existing);
let flat = TextOpacity::<()>::blend_foreground_over_background(
existing,
bg_for_text,
opacity,
);
s.color = Some(flat.to_simple_opaque());
style_changed = true;
}
if let Some(tint) = style.tint {
if let Some(bg) = s.bgcolor {
let bg = crate::style::color_from_simple(bg);
let blended =
Tint::<()>::blend_color_with_percent(bg, tint.color, tint.percent);
s.bgcolor = Some(blended.to_simple_opaque());
style_changed = true;
}
if let Some(fg) = s.color {
let fg = crate::style::color_from_simple(fg);
let blended =
Tint::<()>::blend_color_with_percent(fg, tint.color, tint.percent);
s.color = Some(blended.to_simple_opaque());
style_changed = true;
}
}
if style_changed || seg.style.is_some() {
seg.style = Some(s);
}
seg
})
.collect()
}
pub(crate) fn apply_widget_opacity_to_segments(
segments: Segments,
opacity_percent: u8,
parent_style: Option<Style>,
) -> Segments {
if opacity_percent >= 100 {
return segments;
}
let opacity = (opacity_percent as f32 / 100.0).clamp(0.0, 1.0);
let fallback_bg = crate::style::parse_color_like("$background");
let parent_bg = crate::css::current_composited_background()
.or_else(|| parent_style.and_then(|style| style.bg))
.or(fallback_bg)
.unwrap_or(crate::style::Color::rgb(0, 0, 0));
segments
.into_iter()
.map(|mut seg| {
if seg.control.is_some() {
return seg;
}
let mut style_changed = false;
let mut style = seg.style.unwrap_or_else(rich_rs::Style::new);
let original_bg = style.bgcolor.map(crate::style::color_from_simple);
let original_fg = style.color.map(crate::style::color_from_simple);
if let Some(bg) = original_bg {
let bg = TextOpacity::<()>::apply_alpha(bg, opacity);
let flat_bg = bg.flatten_over(parent_bg);
style.bgcolor = Some(flat_bg.to_simple_opaque());
style_changed = true;
}
if let Some(fg) = original_fg {
let fg_source = fg;
let fg = TextOpacity::<()>::apply_alpha(fg, opacity);
let mut flat_fg = fg.flatten_over(parent_bg);
if let (Some(src_bg), Some(dst_bg)) = (
original_bg,
style.bgcolor.map(crate::style::color_from_simple),
) {
if src_bg == fg_source {
flat_fg = dst_bg;
}
}
style.color = Some(flat_fg.to_simple_opaque());
style_changed = true;
}
if style_changed || seg.style.is_some() {
seg.style = Some(style);
}
seg
})
.collect()
}