#![allow(dead_code)]
use guido::layout::{Constraints, Size};
use guido::prelude::*;
use guido::renderer::{DrawCommand, RenderNode};
use guido::tree::{Tree, WidgetId};
use guido::widgets::widget::EventResponse;
pub const LADDER: [Shadow; 6] = [
Shadow::none(),
Shadow::new((0.0, 1.0), 3.0, 0.0, Color::rgba(0.0, 0.0, 0.0, 0.12)),
Shadow::new((0.0, 2.0), 4.0, 0.0, Color::rgba(0.0, 0.0, 0.0, 0.16)),
Shadow::new((0.0, 3.0), 6.0, 0.0, Color::rgba(0.0, 0.0, 0.0, 0.19)),
Shadow::new((0.0, 4.0), 8.0, 0.0, Color::rgba(0.0, 0.0, 0.0, 0.20)),
Shadow::new((0.0, 6.0), 10.0, 0.0, Color::rgba(0.0, 0.0, 0.0, 0.22)),
];
pub struct Harness {
pub tree: Tree,
pub root: WidgetId,
}
impl Harness {
pub fn new(widget: impl Widget + 'static) -> Self {
let mut tree = Tree::new();
let root = tree.register(Box::new(widget));
tree.with_widget_mut(root, |w, id, t| w.register_children(t, id));
Self { tree, root }
}
pub fn laid_out(widget: impl Widget + 'static, width: f32, height: f32) -> Self {
let mut surface = Self::new(widget);
surface.lay_out(width, height);
surface
}
pub fn focused(widget: impl Widget + 'static, width: f32, height: f32) -> Self {
guido::reactive::focus::clear_focus();
let harness = Self::laid_out(widget, width, height);
guido::reactive::focus::request_focus(&harness.tree, harness.root);
harness
}
pub fn lay_out(&mut self, width: f32, height: f32) -> Size {
let root = self.root;
self.tree
.layout_widget(root, Constraints::new(0.0, 0.0, width, height))
.expect("the root is registered")
}
pub fn send(&mut self, event: Event) -> EventResponse {
let root = self.root;
guido::reactive::diagnostics::snapshot_zone(|| {
self.tree
.with_widget_mut(root, |w, id, t| w.event(t, id, &event))
.expect("the root is registered")
})
}
pub fn send_at(&mut self, event: Event, at: std::time::Instant) -> EventResponse {
self.tree.set_event_instant(Some(at));
let response = self.send(event);
self.tree.set_event_instant(None);
response
}
pub fn click(&mut self, x: f32, y: f32) {
self.send(Event::mouse_down(x, y, MouseButton::Left));
self.send(Event::mouse_up(x, y, MouseButton::Left));
}
pub fn painted_rects(&mut self) -> Vec<Rect> {
self.painted_rounded_rects()
.into_iter()
.map(|(rect, _)| rect)
.collect()
}
pub fn painted_rounded_rects(&mut self) -> Vec<(Rect, Color)> {
fn collect(node: &RenderNode, out: &mut Vec<(Rect, Color)>) {
for cmd in &node.commands {
if let DrawCommand::RoundedRect { rect, color, .. } = &**cmd {
out.push((*rect, *color));
}
}
for child in &node.children {
collect(child, out);
}
}
let mut out = Vec::new();
collect(&self.paint(), &mut out);
out
}
pub fn paint(&mut self) -> RenderNode {
let root = self.root;
self.tree.set_frame_instant(Some(std::time::Instant::now()));
let mut node = RenderNode::new(root.as_u64());
self.tree.paint_widget(root, &mut node);
self.tree.set_frame_instant(None);
node
}
pub fn advance(&mut self, at: std::time::Instant) {
self.tree.set_frame_instant(Some(at));
for id in self.tree.collect_subtree_post_order(self.root) {
self.tree
.with_widget_mut(id, |w, id, t| w.advance_animations(t, id));
}
self.tree.set_frame_instant(None);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Blessing {
Write,
Compare,
}
pub fn blessing(update: bool, rebless: bool, exists: bool) -> Blessing {
match (update, rebless, exists) {
(_, true, _) => Blessing::Write,
(true, false, false) => Blessing::Write,
(true, false, true) => Blessing::Compare,
(false, false, _) => Blessing::Compare,
}
}
pub fn blessing_from_env(kind: &str, exists: bool) -> Blessing {
let asked = |prefix: &str| std::env::var_os(format!("{prefix}_{kind}")).is_some();
let update = asked("UPDATE");
let decision = blessing(update, asked("REBLESS"), exists);
if update && decision == Blessing::Compare {
eprintln!(
"UPDATE_{kind} declined: this reference already exists, and rewriting one \
makes a failing test pass without changing anything back. Read the diff \
the comparison is about to print; if the change is intended, that is \
REBLESS_{kind}=1 and the `golden-update` label on the pull request."
);
}
decision
}
pub fn write_if_blessed(path: &std::path::Path, blessing: Blessing, write: impl FnOnce()) -> bool {
match blessing {
Blessing::Write => {
write();
assert!(
path.exists(),
"blessed {} and it is still not there",
path.display()
);
true
}
Blessing::Compare => false,
}
}
pub fn one_play_at_a_time() -> std::sync::MutexGuard<'static, ()> {
static PLAYING: std::sync::Mutex<()> = std::sync::Mutex::new(());
PLAYING
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub const COUNTS_AGREE_TO: u64 = 1000;
pub const BYTES_AGREE_TO: u64 = 20;
pub fn heap_figures_agree(first: u64, second: u64, what: &str, in_parts: u64) {
let margin = (first.max(second) / in_parts).max(16);
assert!(
first.abs_diff(second) <= margin,
"the same script over the same tree asked the allocator for {what} \
{first} one run and {second} the next, which is further apart than the \
{margin} the driver's own threads account for"
);
}
#[cfg(feature = "testing")]
pub fn headless() -> Option<guido::testing::Headless> {
match guido::testing::Headless::new() {
Some(app) => Some(app),
None if std::env::var_os("GUIDO_GPU_REQUIRED").is_some() => {
panic!("GUIDO_GPU_REQUIRED is set and no GPU adapter was found")
}
None => {
eprintln!("no GPU adapter; skipping");
None
}
}
}