use std::cell::Cell;
use std::rc::Rc;
use guido::prelude::*;
use guido::widget_prelude::*;
struct Bar {
extent: Signal<f32>,
measured: f32,
layouts: Rc<Cell<u32>>,
pointed_at: Rc<Cell<Option<Option<Point>>>>,
}
impl Widget for Bar {
fn layout(&mut self, _ctx: &mut LayoutCtx, _constraints: Constraints) -> Size {
self.layouts.set(self.layouts.get() + 1);
let extent = self.extent.get();
self.measured = extent;
Size::new(extent, extent)
}
fn event(&mut self, _tree: &mut Tree, _id: WidgetId, event: &Event) -> EventResponse {
if let Event::MouseMove { at, .. } = event {
self.pointed_at.set(Some(*at));
return EventResponse::Handled;
}
EventResponse::Ignored
}
fn paint(&self, ctx: &mut PaintContext) {
ctx.draw_rounded_rect(
Rect::new(0.0, 0.0, self.measured, self.measured),
Color::RED,
0.0,
);
}
}
fn lay_out(widget: impl Widget + 'static) -> (Tree, WidgetId, Size) {
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));
let size = tree
.layout_widget(root, Constraints::new(0.0, 0.0, 400.0, 400.0))
.expect("root is registered");
(tree, root, size)
}
#[test]
fn a_widget_from_outside_the_crate_lays_out_and_paints() {
let extent = create_signal(20.0f32);
let (tree, root, size) = lay_out(Bar {
extent: extent.into(),
measured: 0.0,
layouts: Rc::new(Cell::new(0)),
pointed_at: Rc::new(Cell::new(None)),
});
assert_eq!(size, Size::new(20.0, 20.0));
let mut node = RenderNode::new(root.as_u64());
tree.paint_widget(root, &mut node);
assert!(!node.commands.is_empty(), "the widget drew nothing");
}
#[test]
fn it_re_measures_from_the_signal_it_read() {
let extent = create_signal(20.0f32);
let (mut tree, root, _) = lay_out(Bar {
extent: extent.into(),
measured: 0.0,
layouts: Rc::new(Cell::new(0)),
pointed_at: Rc::new(Cell::new(None)),
});
extent.set(40.0);
tree.mark_needs_layout(root);
let size = tree
.layout_widget(root, Constraints::new(0.0, 0.0, 400.0, 400.0))
.expect("root is registered");
assert_eq!(size, Size::new(40.0, 40.0));
}
#[test]
fn a_widget_from_outside_the_crate_can_tell_a_position_from_none() {
let extent = create_signal(20.0f32);
let pointed_at = Rc::new(Cell::new(None));
let (mut tree, root, _) = lay_out(Bar {
extent: extent.into(),
measured: 0.0,
layouts: Rc::new(Cell::new(0)),
pointed_at: Rc::clone(&pointed_at),
});
let mut send = |at| {
tree.with_widget_mut(root, |w, id, t| {
w.event(
t,
id,
&Event::MouseMove {
at,
pointer: PointerKind::Mouse,
},
)
});
};
send(Some(Point::new(3.0, 4.0)));
assert_eq!(
pointed_at.get(),
Some(Some(Point::new(3.0, 4.0))),
"a positioned move arrives with its position"
);
send(None);
assert_eq!(
pointed_at.get(),
Some(None),
"and one that lost its position arrives without it, rather than not \
arriving or arriving at the origin"
);
}
#[test]
fn a_leaf_with_no_check_of_its_own_is_not_laid_out_twice_for_one_answer() {
let extent = create_signal(20.0f32);
let layouts = Rc::new(Cell::new(0));
let (mut tree, root, _) = lay_out(Bar {
extent: extent.into(),
measured: 0.0,
layouts: Rc::clone(&layouts),
pointed_at: Rc::new(Cell::new(None)),
});
assert_eq!(layouts.get(), 1, "the first layout runs");
tree.layout_widget(root, Constraints::new(0.0, 0.0, 400.0, 400.0));
assert_eq!(
layouts.get(),
1,
"the same constraints, and nothing dirty: there is nothing to ask"
);
}
#[cfg(feature = "testing")]
mod a_paint_that_opens_no_scope {
use super::*;
use guido::testing::Headless;
struct Frame {
child: Option<Box<dyn Widget>>,
child_id: Option<WidgetId>,
}
impl Widget for Frame {
fn register_children(&mut self, tree: &mut Tree, id: WidgetId) {
let child = self.child.take().expect("registered once");
let child_id = tree.register(child);
tree.set_parent(child_id, id);
self.child_id = Some(child_id);
}
fn layout(&mut self, ctx: &mut LayoutCtx, constraints: Constraints) -> Size {
let child = self.child_id.expect("registered before layout");
let size = ctx.layout_child(child, constraints).unwrap_or_default();
ctx.tree().set_origin(child, 0.0, 0.0);
size
}
fn paint(&self, ctx: &mut PaintContext) {
let child = self.child_id.expect("registered before paint");
ctx.paint_child(child, &ChildPaintOptions::default());
}
}
struct Swatch {
colour: Signal<Color>,
}
impl Widget for Swatch {
fn layout(&mut self, _ctx: &mut LayoutCtx, constraints: Constraints) -> Size {
Size::new(constraints.max_width, constraints.max_height)
}
fn paint(&self, ctx: &mut PaintContext) {
let bounds = Rect::new(0.0, 0.0, 100.0, 32.0);
ctx.draw_rounded_rect(bounds, self.colour.get(), 0.0);
}
}
#[test]
fn a_widget_that_opens_no_paint_scope_still_follows_its_signal() {
let Some(mut app) = Headless::new() else {
if std::env::var_os("GUIDO_GPU_REQUIRED").is_some() {
panic!("GUIDO_GPU_REQUIRED is set and no GPU adapter was found");
}
eprintln!("no GPU adapter; skipping");
return;
};
let colour = create_signal(Color::rgb(1.0, 0.0, 0.0));
let surface = app.surface(
SurfaceConfig::new()
.height(32)
.anchor(Anchor::TOP | Anchor::LEFT | Anchor::RIGHT),
move || Frame {
child: Some(Box::new(Swatch {
colour: colour.into(),
})),
child_id: None,
},
);
app.configure(surface, 100, 32, 1.0);
app.step();
let [r, _, b, _] = app.read_pixel(surface, 50, 16);
assert!(
r > b,
"the first frame drew the colour it was given: {r} vs {b}"
);
colour.set(Color::rgb(0.0, 0.0, 1.0));
app.step();
let [r, _, b, _] = app.read_pixel(surface, 50, 16);
assert!(
b > r,
"the write reached nothing: the widget still shows the colour it \
was built with. Its paint read the signal, so the read belongs to \
it — but no scope was open to hear it ({r} vs {b})"
);
}
}