#![expect(missing_docs, reason = "This is example code")]
use crate as masonry;
use masonry::kurbo::Size;
use masonry::peniko::Color;
use masonry::core::{
AccessEvent, EventCtx, PointerButton, PointerEvent, PropertiesMut, TextEvent, Widget,
};
use masonry::core::{Update, UpdateCtx};
use masonry::core::{BoxConstraints, LayoutCtx};
use masonry::accesskit::{Node, Role};
use masonry::core::{AccessCtx, PaintCtx, PropertiesRef};
use masonry::kurbo::Affine;
use masonry::peniko::Fill;
use masonry::vello::Scene;
use masonry::core::WidgetId;
use tracing::{Span, trace_span};
use masonry::core::{ChildrenIds, RegisterCtx};
use masonry::core::WidgetMut;
use masonry::properties::Background;
pub struct ColorRectangle {
size: Size,
color: Color,
}
impl ColorRectangle {
pub fn new(size: Size, color: Color) -> Self {
Self { size, color }
}
}
impl ColorRectangle {
pub fn set_color(this: &mut WidgetMut<'_, Self>, color: Color) {
this.widget.color = color;
this.ctx.request_paint_only();
}
pub fn set_size(this: &mut WidgetMut<'_, Self>, size: Size) {
this.widget.size = size;
this.ctx.request_layout();
}
}
#[derive(Debug)]
pub struct ColorRectanglePress;
impl Widget for ColorRectangle {
type Action = ColorRectanglePress;
fn on_pointer_event(
&mut self,
ctx: &mut EventCtx<'_>,
_props: &mut PropertiesMut<'_>,
event: &PointerEvent,
) {
match event {
PointerEvent::Down(b) if b.button == Some(PointerButton::Primary) => {
ctx.submit_action::<Self::Action>(ColorRectanglePress);
}
_ => {}
}
}
fn on_text_event(
&mut self,
_ctx: &mut EventCtx<'_>,
_props: &mut PropertiesMut<'_>,
_event: &TextEvent,
) {
}
fn on_access_event(
&mut self,
ctx: &mut EventCtx<'_>,
_props: &mut PropertiesMut<'_>,
event: &AccessEvent,
) {
match event.action {
accesskit::Action::Click => {
ctx.submit_action::<Self::Action>(ColorRectanglePress);
}
_ => {}
}
}
fn on_anim_frame(
&mut self,
_ctx: &mut UpdateCtx<'_>,
_props: &mut PropertiesMut<'_>,
_interval: u64,
) {
}
#[cfg(false)] fn update(
&mut self,
_ctx: &mut UpdateCtx<'_>,
_props: &mut PropertiesMut<'_>,
_event: &Update,
) {
}
fn layout(
&mut self,
_ctx: &mut LayoutCtx<'_>,
_props: &mut PropertiesMut<'_>,
bc: &BoxConstraints,
) -> Size {
bc.constrain(self.size)
}
#[cfg(false)] fn paint(&mut self, ctx: &mut PaintCtx<'_>, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let rect = ctx.size().to_rect();
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
self.color,
Some(Affine::IDENTITY),
&rect,
);
}
fn accessibility_role(&self) -> Role {
Role::Button
}
fn accessibility(
&mut self,
_ctx: &mut AccessCtx<'_>,
_props: &PropertiesRef<'_>,
node: &mut Node,
) {
node.add_action(accesskit::Action::Click);
}
fn make_trace_span(&self, id: WidgetId) -> Span {
trace_span!("ColorRectangle", id = id.trace())
}
fn register_children(&mut self, _ctx: &mut RegisterCtx<'_>) {}
fn children_ids(&self) -> ChildrenIds {
ChildrenIds::new()
}
fn paint(&mut self, ctx: &mut PaintCtx<'_>, _props: &PropertiesRef<'_>, scene: &mut Scene) {
let rect = ctx.size().to_rect();
let color = if ctx.is_hovered() {
Color::WHITE
} else {
self.color
};
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
color,
Some(Affine::IDENTITY),
&rect,
);
}
fn update(&mut self, ctx: &mut UpdateCtx<'_>, _props: &mut PropertiesMut<'_>, event: &Update) {
match event {
Update::HoveredChanged(_) => {
ctx.request_render();
}
_ => {}
}
}
}
#[expect(dead_code, reason = "example code")]
#[expect(clippy::trivially_copy_pass_by_ref, reason = "example code")]
impl ColorRectangle {
fn paint(&mut self, ctx: &mut PaintCtx<'_>, props: &PropertiesRef<'_>, scene: &mut Scene) {
let background = props.get::<Background>();
let rect = ctx.size().to_rect();
scene.fill(
Fill::NonZero,
Affine::IDENTITY,
&background.get_peniko_brush_for_rect(rect),
Some(Affine::IDENTITY),
&rect,
);
}
}
#[expect(dead_code, reason = "example code")]
fn set_bg(color_rectangle_mut: WidgetMut<'_, ColorRectangle>) {
let mut color_rectangle_mut: WidgetMut<'_, ColorRectangle> = color_rectangle_mut;
let bg = Background::Color(masonry::palette::css::BLUE);
color_rectangle_mut.insert_prop(bg);
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_debug_snapshot;
use masonry::testing::TestHarness;
use masonry::theme::default_property_set;
use masonry_core::core::NewWidget;
use masonry::testing::assert_render_snapshot;
const BLUE: Color = Color::from_rgb8(0, 0, u8::MAX);
#[test]
fn simple_rect() {
let widget = ColorRectangle::new(Size::new(20.0, 20.0), BLUE);
let mut harness = TestHarness::create(default_property_set(), NewWidget::new(widget));
assert_debug_snapshot!(harness.root_widget());
assert_render_snapshot!(harness, "rect_blue_rectangle");
}
#[test]
fn hovered() {
let widget = NewWidget::new(ColorRectangle::new(Size::new(20.0, 20.0), BLUE));
let mut harness = TestHarness::create(default_property_set(), widget);
let rect_id = harness.root_id();
harness.mouse_move_to(rect_id);
assert_render_snapshot!(harness, "rect_hovered_rectangle");
}
#[test]
fn edit_rect() {
const RED: Color = Color::from_rgb8(u8::MAX, 0, 0);
let widget = NewWidget::new(ColorRectangle::new(Size::new(20.0, 20.0), BLUE));
let mut harness = TestHarness::create(default_property_set(), widget);
harness.edit_root_widget(|mut rect| {
ColorRectangle::set_size(&mut rect, Size::new(50.0, 50.0));
ColorRectangle::set_color(&mut rect, RED);
});
assert_render_snapshot!(harness, "rect_big_red_rectangle");
}
#[test]
fn on_click() {
let widget = NewWidget::new(ColorRectangle::new(Size::new(20.0, 20.0), BLUE));
let mut harness = TestHarness::create(default_property_set(), widget);
let rect_id = harness.root_id();
harness.mouse_click_on(rect_id);
assert!(matches!(
harness.pop_action::<ColorRectanglePress>(),
Some((ColorRectanglePress, _))
));
}
}