use std::sync::Arc;
use compact_str::CompactString;
use indexmap::IndexMap;
use crate::html::css_parse::DynamicState;
use crate::source::SharedSourceText;
use crate::tdom::node::NodeId;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectionMode {
Flow,
Block,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct DirtyInfo {
pub nodes: Vec<crate::tdom::node::NodeId>,
pub injected_css: IndexMap<crate::tdom::node::NodeId, String>,
}
impl DirtyInfo {
pub fn is_empty(&self) -> bool {
self.nodes.is_empty() && self.injected_css.is_empty()
}
}
#[derive(Debug, Clone)]
pub enum RenderCmd {
Rerender {
scroll_y: u32,
},
Redraw {
scroll_y: u32,
zoom: f32,
},
RenderArena {
arena: Arc<crate::tdom::node::NodeArena>,
root_id: crate::tdom::node::NodeId,
scroll_y: u32,
zoom: f32,
dynamic: DynamicState,
dirty: DirtyInfo,
},
RenderDocument {
arena: Arc<crate::tdom::node::NodeArena>,
root_id: crate::tdom::node::NodeId,
document_css: SharedSourceText,
scroll_y: u32,
zoom: f32,
dirty: DirtyInfo,
},
ReloadCss {
css: SharedSourceText,
},
Hover {
node_id: Option<NodeId>,
},
Focus {
node_id: Option<NodeId>,
},
RenderInput {
node_id: NodeId,
value: CompactString,
cursor: usize,
focused: bool,
selection: Option<(usize, usize)>,
},
RenderScroll {
node_id: NodeId,
scroll_y: u32,
},
RenderScrollX {
node_id: NodeId,
scroll_x: u32,
},
ScrollAnimate {
target: super::scroll_anim::ScrollTarget,
to: u32,
},
ScrollSet {
to: u32,
},
ScrollIntoView {
node_id: NodeId,
block: super::scroll_anim::ScrollIntoViewBlock,
smooth: bool,
},
FocusRequest {
node_id: NodeId,
},
BlurRequest {
node_id: NodeId,
},
SelectOpen {
node_id: NodeId,
filter: Option<CompactString>,
},
SelectClose {
node_id: NodeId,
},
SelectMove {
node_id: NodeId,
highlight: usize,
},
TextSelection {
anchor: Option<(u32, u32)>,
end: Option<(u32, u32)>,
mode: SelectionMode,
},
Quit,
}
impl RenderCmd {
pub fn refresh_dynamic_focus(&mut self, focused: Option<NodeId>) {
if let RenderCmd::RenderArena { dynamic, .. } = self {
dynamic.focused = focused;
}
}
}
fn merge_dirty(target: &mut DirtyInfo, source: DirtyInfo) {
target.nodes.extend(source.nodes);
target.injected_css.extend(source.injected_css);
}
#[derive(Debug, Default)]
pub struct DrainedBatch {
inputs: Vec<RenderCmd>,
scrolls: Vec<RenderCmd>,
css_reloads: Vec<RenderCmd>,
selects: Vec<RenderCmd>,
text_selection: Option<RenderCmd>,
hover: Option<RenderCmd>,
focus: Option<RenderCmd>,
snapshot: Option<RenderCmd>,
quit: bool,
}
impl DrainedBatch {
pub fn new() -> Self {
Self::default()
}
pub fn absorb(&mut self, cmd: RenderCmd) {
match cmd {
RenderCmd::RenderInput { .. } => self.inputs.push(cmd),
RenderCmd::RenderScroll { .. } | RenderCmd::RenderScrollX { .. } => {
self.scrolls.push(cmd)
}
RenderCmd::ScrollAnimate { .. }
| RenderCmd::ScrollSet { .. }
| RenderCmd::ScrollIntoView { .. } => {}
sel @ (RenderCmd::SelectOpen { .. }
| RenderCmd::SelectClose { .. }
| RenderCmd::SelectMove { .. }) => self.selects.push(sel),
css @ RenderCmd::ReloadCss { .. } => self.css_reloads.push(css),
other @ RenderCmd::TextSelection { .. } => {
self.text_selection = Some(other);
}
other @ RenderCmd::Hover { .. } => match self.snapshot.as_ref() {
Some(
RenderCmd::RenderArena { .. }
| RenderCmd::Redraw { .. }
| RenderCmd::Rerender { .. },
) => self.hover = Some(other),
Some(RenderCmd::RenderDocument { .. }) => {}
_ if !self.quit => self.snapshot = Some(other),
_ => {}
},
RenderCmd::Focus { node_id } => match self.snapshot.as_ref() {
Some(RenderCmd::RenderArena { .. }) => {
if let Some(snapshot) = self.snapshot.as_mut() {
snapshot.refresh_dynamic_focus(node_id);
}
}
Some(RenderCmd::Redraw { .. } | RenderCmd::Rerender { .. }) => {
self.focus = Some(RenderCmd::Focus { node_id });
}
Some(RenderCmd::RenderDocument { .. }) => {}
_ if !self.quit => self.snapshot = Some(RenderCmd::Focus { node_id }),
_ => {}
},
RenderCmd::Rerender { scroll_y } => match self.snapshot.as_mut() {
Some(
RenderCmd::RenderArena {
scroll_y: arena_scroll,
..
}
| RenderCmd::RenderDocument {
scroll_y: arena_scroll,
..
},
) => {
*arena_scroll = scroll_y;
}
_ => self.snapshot = Some(RenderCmd::Rerender { scroll_y }),
},
RenderCmd::Redraw { scroll_y, zoom } => match self.snapshot.as_mut() {
Some(
RenderCmd::RenderArena {
scroll_y: arena_scroll,
zoom: arena_zoom,
..
}
| RenderCmd::RenderDocument {
scroll_y: arena_scroll,
zoom: arena_zoom,
..
},
) => {
*arena_scroll = scroll_y;
*arena_zoom = zoom;
}
_ => self.snapshot = Some(RenderCmd::Redraw { scroll_y, zoom }),
},
RenderCmd::RenderArena {
arena,
root_id,
scroll_y,
zoom,
dynamic,
dirty,
} => match self.snapshot.as_mut() {
Some(RenderCmd::RenderDocument {
arena: doc_arena,
root_id: doc_root,
scroll_y: doc_scroll,
zoom: doc_zoom,
dirty: doc_dirty,
..
}) => {
*doc_arena = arena;
*doc_root = root_id;
*doc_scroll = scroll_y;
*doc_zoom = zoom;
merge_dirty(doc_dirty, dirty);
}
_ if !self.quit => {
self.snapshot = Some(RenderCmd::RenderArena {
arena,
root_id,
scroll_y,
zoom,
dynamic,
dirty,
});
}
_ => {}
},
other => {
self.quit |= matches!(other, RenderCmd::Quit);
self.snapshot = Some(other);
}
}
}
pub fn quit_requested(&self) -> bool {
self.quit
}
pub fn drain_inputs(&mut self) -> impl Iterator<Item = RenderCmd> {
std::mem::take(&mut self.inputs).into_iter()
}
pub fn drain_scrolls(&mut self) -> impl Iterator<Item = RenderCmd> {
std::mem::take(&mut self.scrolls).into_iter()
}
pub fn drain_css_reloads(&mut self) -> impl Iterator<Item = RenderCmd> {
std::mem::take(&mut self.css_reloads).into_iter()
}
pub fn drain_selects(&mut self) -> impl Iterator<Item = RenderCmd> {
std::mem::take(&mut self.selects).into_iter()
}
pub fn take_text_selection(&mut self) -> Option<RenderCmd> {
self.text_selection.take()
}
pub fn take_snapshot(&mut self) -> Option<RenderCmd> {
self.snapshot.take()
}
pub fn take_hover(&mut self) -> Option<RenderCmd> {
self.hover.take()
}
pub fn take_focus(&mut self) -> Option<RenderCmd> {
self.focus.take()
}
}