use std::cell::RefCell;
use std::fmt;
use std::mem;
use taria::Node;
use crate::TariaLayer;
use crate::layer::drop_iteratively;
pub struct FrameRecorder<'a> {
layer: &'a mut TariaLayer,
nodes: Recorded,
}
struct Recorded(RefCell<Vec<Node>>);
impl Drop for Recorded {
fn drop(&mut self) {
mem::take(self.0.get_mut())
.into_iter()
.for_each(drop_iteratively);
}
}
impl fmt::Debug for FrameRecorder<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut out = f.debug_struct("FrameRecorder");
out.field("layer", &*self.layer);
match self.nodes.0.try_borrow() {
Ok(nodes) => out.field("recorded", &nodes.len()),
Err(_) => out.field("recorded", &format_args!("<being recorded>")),
};
out.finish_non_exhaustive()
}
}
impl<'a> FrameRecorder<'a> {
pub(crate) fn new(layer: &'a mut TariaLayer) -> Self {
Self {
layer,
nodes: Recorded(RefCell::new(Vec::new())),
}
}
pub fn push(&mut self, node: Node) {
self.nodes.0.get_mut().push(node);
}
pub(crate) fn sink(&self) -> &RefCell<Vec<Node>> {
&self.nodes.0
}
pub fn publish(mut self) {
let nodes = mem::take(self.nodes.0.get_mut());
self.layer.publish_nodes(nodes);
}
}
#[cfg(test)]
mod tests {
use taria::{Node, NodeId, Role};
use crate::test_util::{TestLayer, bind_test_layer};
fn test_layer(label: &str) -> TestLayer {
bind_test_layer("taria-rec-", label)
}
fn child_ids(root: &Node) -> Vec<&str> {
root.children.iter().map(|c| c.id.0.as_str()).collect()
}
#[test]
fn push_preserves_order() {
let mut layer = test_layer("order");
let mut rec = layer.frame();
rec.push(Node::new("first", Role::Text));
rec.push(Node::new("second", Role::List));
rec.push(Node::new("third", Role::Button));
rec.publish();
let snapshot = layer.latest_snapshot().unwrap();
assert_eq!(child_ids(&snapshot.root), ["first", "second", "third"]);
}
#[test]
fn publish_wraps_nodes_in_auto_root() {
let mut layer = test_layer("root");
let mut rec = layer.frame();
rec.push(Node::new("pane", Role::Pane));
rec.publish();
let root = layer.latest_snapshot().unwrap().root;
assert_eq!(root.id, NodeId("app".into()));
assert_eq!(root.role, Role::App);
assert_eq!(root.label.as_deref(), Some("root"));
assert_eq!(root.children.len(), 1);
}
#[test]
fn root_focused_only_when_no_recorded_node_is() {
let mut layer = test_layer("focus");
let mut rec = layer.frame();
rec.push(Node::new("pane", Role::Pane));
rec.publish();
assert!(layer.latest_snapshot().unwrap().root.focused);
let mut rec = layer.frame();
rec.push(
Node::new("pane", Role::Pane).child(Node::new("input", Role::TextInput).focused(true)),
);
rec.publish();
let root = layer.latest_snapshot().unwrap().root;
assert!(!root.focused, "deep focus must unfocus the auto root");
}
#[test]
fn seq_increments_per_published_snapshot() {
let mut layer = test_layer("seq");
let mut rec = layer.frame();
rec.push(Node::new("a", Role::Text));
rec.publish();
assert_eq!(layer.latest_snapshot().unwrap().seq, 1);
let mut rec = layer.frame();
rec.push(Node::new("b", Role::Text));
rec.publish();
assert_eq!(layer.latest_snapshot().unwrap().seq, 2);
}
#[test]
fn identical_snapshot_is_deduped() {
let mut layer = test_layer("dedup");
let mut rec = layer.frame();
rec.push(Node::new("a", Role::Text).label("same"));
rec.publish();
assert_eq!(layer.latest_snapshot().unwrap().seq, 1);
let mut rec = layer.frame();
rec.push(Node::new("a", Role::Text).label("same"));
rec.publish();
assert_eq!(layer.latest_snapshot().unwrap().seq, 1);
let mut rec = layer.frame();
rec.push(Node::new("a", Role::Text).label("changed"));
rec.publish();
assert_eq!(layer.latest_snapshot().unwrap().seq, 2);
}
#[test]
fn a_recorder_left_unpublished_holds_the_layer_only_until_its_last_use() {
let mut layer = test_layer("borrow");
for paused in [true, false] {
let node = Node::new("a", Role::Text);
let mut rec = layer.frame();
rec.push(node);
if paused {
layer.stats();
continue;
}
rec.publish();
}
assert_eq!(layer.latest_snapshot().unwrap().seq, 1);
}
#[test]
fn empty_frame_publishes_bare_root() {
let mut layer = test_layer("empty");
layer.frame().publish();
let root = layer.latest_snapshot().unwrap().root;
assert!(root.children.is_empty());
assert!(root.focused);
}
}