use rdom_core::NodeId;
use rdom_css::{Warning, parse_inline};
use rdom_style::TuiStyle;
use super::declaration::css_text_of;
use crate::TuiDom;
pub(crate) fn sync_from_attribute(dom: &mut TuiDom, id: NodeId) -> Vec<Warning> {
let node = dom.node(id);
let (Some(text), Some(ext)) = (node.get_attribute("style"), node.ext()) else {
return Vec::new();
};
if text == css_text_of(ext.inline_style_or_empty()) {
return Vec::new();
}
let parsed = parse_inline(text);
if let Some(ext) = dom.node_mut(id).ext_mut() {
ext.set_inline_style(parsed.style);
}
parsed.warnings
}
pub(crate) fn write_inline_style(dom: &mut TuiDom, id: NodeId, f: impl FnOnce(&mut TuiStyle)) {
if dom.node(id).ext().is_none() {
return;
}
sync_from_attribute(dom, id);
let css_text = {
let mut node = dom.node_mut(id);
let ext = node.ext_mut().expect("checked above: an element");
f(ext.inline_style_mut());
let style = ext.inline_style.take().map(|b| *b).unwrap_or_default();
ext.set_inline_style(style);
ext.style_dirty = true;
css_text_of(ext.inline_style_or_empty())
};
write_style_attribute(dom, id, &css_text).expect("an element accepts a `style` attribute");
}
pub(crate) fn write_style_attribute(
dom: &mut TuiDom,
id: NodeId,
css_text: &str,
) -> rdom_core::Result<()> {
let _g = super::reentry::ReentryGuard::enter();
dom.set_attribute(id, "style", css_text)
}
#[cfg(test)]
mod tests {
use rdom_core::NodeId;
use rdom_style::{Color, TuiColor, TuiStyle, Value};
use crate::layout::Size;
use crate::node::{TuiNodeExt, TuiNodeMutExt};
use crate::render::{Terminal, TestBackend};
use crate::runtime::app::App;
use crate::{Stylesheet, TuiAccessorsMut, TuiDom};
fn styled_div() -> (TuiDom, NodeId) {
let mut dom: TuiDom = TuiDom::new();
let div = dom.create_element("div");
dom.set_attribute(div, "style", "color: red").unwrap();
dom.append_child(dom.root(), div).unwrap();
(dom, div)
}
fn built(dom: TuiDom) -> App<TestBackend> {
let terminal = Terminal::new(TestBackend::new(20, 4)).unwrap();
let mut app = App::with_backend(dom, Stylesheet::bare(), terminal).unwrap();
app.advance(0).unwrap();
app
}
fn inline(app: &App<TestBackend>, id: NodeId) -> TuiStyle {
app.dom()
.node(id)
.tui_ext()
.unwrap()
.inline_style_or_empty()
.clone()
}
const RED: Value<TuiColor> = Value::Specified(TuiColor::Literal(Color::Rgb(255, 0, 0)));
#[test]
fn a_setter_before_build_survives_seeding_beside_the_markup_style() {
let (mut dom, div) = styled_div();
dom.node_mut(div).set_width(Size::Fixed(7));
let app = built(dom);
let style = inline(&app, div);
assert_eq!(style.width, Some(Value::Specified(Size::Fixed(7))));
assert_eq!(style.fg, Some(RED), "the markup's declaration too");
}
#[test]
fn a_cssom_write_before_build_survives_seeding_beside_the_markup_style() {
let (mut dom, div) = styled_div();
dom.node_mut(div)
.style_mut()
.unwrap()
.set_property("height", "2")
.unwrap();
let app = built(dom);
let style = inline(&app, div);
assert_eq!(style.height, Some(Value::Specified(Size::Fixed(2))));
assert_eq!(style.fg, Some(RED));
}
#[test]
fn a_setter_value_that_does_not_round_trip_survives_seeding() {
let mut dom: TuiDom = TuiDom::new();
let div = dom.create_element("div");
dom.append_child(dom.root(), div).unwrap();
dom.node_mut(div)
.set_inline_style(TuiStyle::new().fg(Color::Indexed(3)));
let app = built(dom);
assert_eq!(
inline(&app, div).fg,
Some(Value::Specified(TuiColor::Literal(Color::Indexed(3))))
);
}
#[test]
fn a_raw_attribute_write_after_a_setter_wins_at_seeding() {
let (mut dom, div) = styled_div();
dom.node_mut(div).set_width(Size::Fixed(7));
dom.set_attribute(div, "style", "height: 3").unwrap();
let app = built(dom);
let style = inline(&app, div);
assert_eq!(style.height, Some(Value::Specified(Size::Fixed(3))));
assert_eq!(style.width, None);
assert_eq!(style.fg, None);
}
}