use std::time::{Duration, Instant};
use rdom_core::NodeId;
use super::StylesheetId;
use super::redraw::Redraw;
use crate::TuiDom;
use crate::cssom::style_elements::StyleElements;
use crate::runtime::builtins::select::Selectedness;
use crate::runtime::builtins::validation::ValidityMarks;
use crate::runtime::caret_blink::CaretBlink;
use crate::style::Stylesheet;
use crate::style::dirty_tracker::DirtyTracker;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum PreludeRun {
Frame,
OffFrame,
}
pub(super) struct PreludeCx<'a> {
pub(super) dom: &'a mut TuiDom,
pub(super) tracker: &'a DirtyTracker,
pub(super) app_sheets: &'a [(StylesheetId, Stylesheet)],
pub(super) redraw: &'a mut Redraw,
pub(super) now: Instant,
pub(super) animation_frame: Duration,
}
pub(super) struct FramePrelude {
pub(super) selectedness: Selectedness,
pub(super) style_elements: StyleElements,
scroll_focus_marked: Option<NodeId>,
validity_marks: ValidityMarks,
pub(super) caret_blink: CaretBlink,
pub(super) smooth_scroll_next: Option<Instant>,
pub(super) touched: bool,
}
impl FramePrelude {
pub(super) fn install(dom: &mut TuiDom) -> Self {
Self {
selectedness: Selectedness::install(dom),
style_elements: StyleElements::install(dom),
scroll_focus_marked: None,
validity_marks: ValidityMarks::default(),
caret_blink: CaretBlink::new(None),
smooth_scroll_next: None,
touched: true,
}
}
pub(super) fn run(&mut self, cx: &mut PreludeCx<'_>, run: PreludeRun) -> u32 {
let mut walks = 0;
let frame = run == PreludeRun::Frame;
self.selectedness.flush(cx.dom);
if self.style_elements.flush(cx.dom) {
self.sheets_changed(cx.tracker, cx.app_sheets, cx.redraw);
}
if frame {
self.mark_scroll_focus(cx.dom);
}
let mut touched = if frame {
std::mem::take(&mut self.touched)
} else {
true
};
if touched {
let walked = self.validity_marks.flush(
cx.dom,
cx.tracker,
self.style_elements
.sheets()
.chain(cx.app_sheets.iter().map(|(_, s)| s)),
);
walks += u32::from(walked);
}
let flipped = self.caret_blink.update(cx.dom, cx.now);
cx.redraw.note_if(flipped, Redraw::Paint);
if !frame {
return walks;
}
if touched || self.smooth_scroll_next.is_some() {
let step = crate::runtime::smooth_scroll::step_all(cx.dom, cx.now);
walks += 1;
cx.redraw.note_if(step.moved, Redraw::Layout);
self.smooth_scroll_next = step.active.then(|| cx.now + cx.animation_frame);
if step.moved {
touched = true;
self.touched = true;
}
}
if touched {
let moved = crate::runtime::scrollbar::moved_since_paint(cx.dom);
walks += 1;
cx.redraw.note_if(moved, Redraw::Layout);
}
cx.redraw
.note_if(cx.tracker.take_paint_dirty(), Redraw::Layout);
cx.redraw
.note_if(cx.tracker.take_selection_dirty(), Redraw::Paint);
walks
}
pub(super) fn after_paint(&mut self, dom: &mut TuiDom, laid_out: bool) -> u32 {
if !laid_out {
return 0;
}
crate::runtime::scrollbar::note_painted(dom);
1
}
pub(super) fn sheets_changed(
&mut self,
tracker: &DirtyTracker,
app_sheets: &[(StylesheetId, Stylesheet)],
redraw: &mut Redraw,
) {
self.sync_sibling_combinators(tracker, app_sheets);
tracker.take_roots();
redraw.note(Redraw::Cascade);
self.validity_marks.sheets_changed();
self.touched = true;
}
pub(super) fn sync_sibling_combinators(
&self,
tracker: &DirtyTracker,
app_sheets: &[(StylesheetId, Stylesheet)],
) {
tracker.set_sibling_triggers(crate::style::sibling_triggers::SiblingTriggers::of_sheets(
self.cascade_order(app_sheets),
));
}
pub(super) fn cascade_order<'a>(
&'a self,
app_sheets: &'a [(StylesheetId, Stylesheet)],
) -> Vec<&'a Stylesheet> {
self.style_elements
.sheets()
.chain(app_sheets.iter().map(|(_, s)| s))
.collect()
}
fn mark_scroll_focus(&mut self, dom: &mut TuiDom) {
let target =
crate::runtime::scrollbar::scroll_focus_target(dom).filter(|_| dom.focus_visible());
if target == self.scroll_focus_marked {
return;
}
if let Some(prev) = self.scroll_focus_marked.take()
&& dom.contains(prev)
{
dom.remove_attribute(prev, crate::runtime::scrollbar::SCROLL_FOCUS_ATTR)
.expect("a live element accepts attribute removal");
}
if let Some(next) = target {
dom.set_attribute(next, crate::runtime::scrollbar::SCROLL_FOCUS_ATTR, "")
.expect("the focused element's ancestor is a live element");
}
self.scroll_focus_marked = target;
}
}