use std::io;
use rdom_core::NodeId;
use super::App;
use super::prelude::{PreludeCx, PreludeRun};
use super::redraw::Redraw;
use crate::TuiDom;
use crate::render::backend::Backend;
use crate::render::{LayoutExt, PaintExt, Rect};
use crate::runtime::animation::AnimationRegistry;
use crate::style::{CascadeExt, Stylesheet};
impl<B: Backend> App<B> {
fn run_prelude(&mut self, run: PreludeRun) {
let now = self.scheduler.borrow().now();
let mut cx = PreludeCx {
dom: &mut self.dom,
tracker: &self.tracker,
app_sheets: &self.stylesheets,
redraw: &mut self.redraw,
now,
animation_frame: std::time::Duration::from_millis(u64::from(self.animation_frame_ms)),
};
let walks = self.prelude.run(&mut cx, run);
self.note_walks(walks);
}
#[cfg_attr(not(test), allow(unused_variables, clippy::unused_self))]
fn note_walks(&mut self, walks: u32) {
#[cfg(test)]
{
self.frame_stats.walks += walks;
}
}
pub fn draw_if_dirty(&mut self) -> io::Result<()> {
let _current = crate::runtime::timers::SchedulerGuard::install(&self.scheduler);
self.run_prelude(PreludeRun::Frame);
let dirty_roots = self.take_dirty_roots();
let redraw = self.redraw;
if redraw == Redraw::Clean && dirty_roots.is_empty() {
crate::rdom_trace!("draw_if_dirty: SKIP (clean, dirty_roots empty)");
return Ok(());
}
crate::rdom_trace!("draw_if_dirty: DRAW (redraw={redraw:?}, dirty_roots={dirty_roots:?})");
let dom = &mut self.dom;
let sheets = self.prelude.cascade_order(&self.stylesheets);
let animations = &mut self.animations;
let mut pass = Pass::default();
self.terminal.draw(|buf| {
pass = style_and_layout(dom, &sheets, animations, redraw, &dirty_roots, buf.area);
dom.paint_dom(buf, buf.area);
Ok(())
})?;
self.note_pass(pass, true);
self.redraw = Redraw::Clean;
let walks = self.prelude.after_paint(&mut self.dom, pass.laid_out);
self.note_walks(walks);
self.dispatch_animation_events();
self.redraw
.note_if(!self.animations.is_empty(), Redraw::Layout);
Ok(())
}
fn take_dirty_roots(&mut self) -> Vec<NodeId> {
let mut dirty_roots = self.tracker.roots_snapshot();
if !dirty_roots.is_empty() {
self.tracker.take_roots();
}
dirty_roots.sort_unstable();
dirty_roots.dedup();
dirty_roots
}
pub(super) fn dispatch_animation_events(&mut self) {
use crate::runtime::animation::{PendingEvent, TransitionEventKind};
let pending = self.animations.take_pending_events();
self.prelude.touched |= !pending.is_empty();
for PendingEvent {
node,
slot,
kind,
property,
elapsed_seconds,
} in pending
{
let event_name = match kind {
TransitionEventKind::Start => "transitionstart",
TransitionEventKind::End => "transitionend",
TransitionEventKind::Cancel => "transitioncancel",
};
let mut ev = rdom_core::Event::new(event_name);
ev.detail =
rdom_core::EventDetail::Transition(Box::new(rdom_core::TransitionDetail::new(
property.css_name(),
elapsed_seconds.into(),
slot.pseudo_element().map(str::to_string),
)));
crate::tui_event::dispatch_event_to_live(&mut self.dom, node, &mut ev);
}
}
pub(super) fn cascade_and_layout(&mut self, area: Rect) {
self.run_prelude(PreludeRun::OffFrame);
let dirty_roots = self.take_dirty_roots();
let redraw = self.redraw.max(Redraw::Layout);
let sheets = self.prelude.cascade_order(&self.stylesheets);
let pass = style_and_layout(
&mut self.dom,
&sheets,
&mut self.animations,
redraw,
&dirty_roots,
area,
);
self.note_pass(pass, false);
if redraw == Redraw::Cascade {
self.redraw = Redraw::Layout;
}
}
#[cfg_attr(not(test), allow(unused_variables, clippy::unused_self))]
fn note_pass(&mut self, pass: Pass, painted: bool) {
#[cfg(test)]
{
let stats = &mut self.frame_stats;
match pass.cascade {
Some(CascadeScope::Full) => stats.full_cascades += 1,
Some(CascadeScope::Subtrees) => stats.subtree_cascades += 1,
None => {}
}
stats.layouts += u32::from(pass.laid_out);
stats.paints += u32::from(painted);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum CascadeScope {
Full,
Subtrees,
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(not(test), allow(dead_code))]
struct Pass {
cascade: Option<CascadeScope>,
laid_out: bool,
}
fn style_and_layout(
dom: &mut TuiDom,
sheets: &[&Stylesheet],
animations: &mut AnimationRegistry,
redraw: Redraw,
dirty_roots: &[NodeId],
area: Rect,
) -> Pass {
let now = std::time::Instant::now();
let cascade = if redraw == Redraw::Cascade {
dom.cascade_all(sheets);
Some(CascadeScope::Full)
} else if !dirty_roots.is_empty() {
dom.cascade_subtrees_all(sheets, dirty_roots);
Some(CascadeScope::Subtrees)
} else {
None
};
if cascade.is_some() {
crate::runtime::animation::diff_and_register(dom, animations, now);
}
let laid_out = cascade.is_some() || redraw >= Redraw::Layout;
if laid_out {
animations.advance(dom, now);
dom.layout_dom(area);
if crate::runtime::scrollbar::service_caret_reveal(dom) {
dom.layout_dom(area);
}
}
Pass { cascade, laid_out }
}