zng_wgt_checkerboard/
lib.rs

1#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
3//!
4//! Checkerboard widget, properties and nodes.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use std::ops;
13
14use zng_color::COLOR_SCHEME_VAR;
15use zng_wgt::prelude::{
16    gradient::{RenderExtendMode, RenderGradientStop},
17    *,
18};
19
20/// A checkerboard visual.
21///
22/// This widget draws a checkerboard pattern, with configurable dimensions and colors.
23#[widget($crate::Checkerboard)]
24pub struct Checkerboard(WidgetBase);
25impl Checkerboard {
26    fn widget_intrinsic(&mut self) {
27        self.widget_builder().push_build_action(|wgt| wgt.set_child(self::node()));
28    }
29}
30
31/// Checker board colors.
32///
33/// See [`colors`](fn@colors) for more details.
34#[derive(Debug, Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
35pub struct Colors(pub [LightDark; 2]);
36impl ops::Deref for Colors {
37    type Target = [LightDark; 2];
38
39    fn deref(&self) -> &Self::Target {
40        &self.0
41    }
42}
43impl_from_and_into_var! {
44    fn from<C: Into<LightDark>>([c0, c1]: [C; 2]) -> Colors {
45        Colors([c0.into(), c1.into()])
46    }
47    fn from<C0: Into<LightDark>, C1: Into<LightDark>>((c0, c1): (C0, C1)) -> Colors {
48        Colors([c0.into(), c1.into()])
49    }
50}
51
52context_var! {
53    /// The checkerboard colors.
54    pub static COLORS_VAR: Colors = [
55        light_dark(rgb(202, 202, 204), rgb(20, 20, 20)),
56        light_dark(rgb(253, 253, 253), rgb(40, 40, 40)),
57    ];
58
59    /// Offset applied to the checkerboard pattern.
60    ///
61    /// Default is no offset, `0`.
62    pub static ORIGIN_VAR: Point = Point::zero();
63
64    /// The size of one color rectangle in the checkerboard.
65    ///
66    /// Default is `10`.
67    pub static SIZE_VAR: Size = 10;
68}
69
70/// Set both checkerboard colors.
71///
72/// The values are the interchanging colors for a given color scheme, for example in the dark
73/// color scheme the `(colors[0].dark, colors[1].dark)` colors are used.
74///
75/// This property sets [`COLORS_VAR`] for all inner checkerboard widgets.
76#[property(CONTEXT, default(COLORS_VAR), widget_impl(Checkerboard))]
77pub fn colors(child: impl UiNode, colors: impl IntoVar<Colors>) -> impl UiNode {
78    with_context_var(child, COLORS_VAR, colors)
79}
80
81/// Set the size of a checkerboard color rectangle.
82///
83/// This property sets the [`SIZE_VAR`] for all inner checkerboard widgets.
84#[property(CONTEXT, default(SIZE_VAR), widget_impl(Checkerboard))]
85pub fn cb_size(child: impl UiNode, size: impl IntoVar<Size>) -> impl UiNode {
86    with_context_var(child, SIZE_VAR, size)
87}
88
89/// Sets the offset of the checkerboard pattern.
90///
91/// Relative values are resolved in the context of a [`cb_size`](fn@cb_size).
92///
93/// This property sets the [`ORIGIN_VAR`] for all inner checkerboard widgets.
94#[property(CONTEXT, default(ORIGIN_VAR), widget_impl(Checkerboard))]
95pub fn cb_origin(child: impl UiNode, offset: impl IntoVar<Point>) -> impl UiNode {
96    with_context_var(child, ORIGIN_VAR, offset)
97}
98
99/// Checkerboard node.
100///
101/// The node is configured by the contextual variables defined in the widget.
102pub fn node() -> impl UiNode {
103    let mut render_size = PxSize::zero();
104    let mut tile_origin = PxPoint::zero();
105    let mut tile_size = PxSize::zero();
106
107    match_node_leaf(move |op| match op {
108        UiNodeOp::Init => {
109            WIDGET
110                .sub_var_render(&COLORS_VAR)
111                .sub_var_render(&COLOR_SCHEME_VAR)
112                .sub_var_layout(&SIZE_VAR)
113                .sub_var_layout(&ORIGIN_VAR);
114        }
115        UiNodeOp::Measure { desired_size, .. } => {
116            *desired_size = LAYOUT.constraints().fill_size();
117        }
118        UiNodeOp::Layout { final_size, .. } => {
119            *final_size = LAYOUT.constraints().fill_size();
120            if *final_size != render_size {
121                render_size = *final_size;
122                WIDGET.render();
123            }
124
125            let mut ts = SIZE_VAR.layout_dft(PxSize::splat(Px(4)));
126            let to = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(ts), || ORIGIN_VAR.layout());
127
128            // each gradient tile has 4 color rectangles.
129            ts *= 2.fct();
130
131            if tile_origin != to || tile_size != ts {
132                tile_origin = to;
133                tile_size = ts;
134
135                WIDGET.render();
136            }
137        }
138        UiNodeOp::Render { frame } => {
139            let [c0, c1] = COLORS_VAR.get().0;
140            let sch = COLOR_SCHEME_VAR.get();
141            let colors = [c0[sch], c1[sch]];
142
143            frame.push_conic_gradient(
144                PxRect::from_size(render_size),
145                tile_size.to_vector().to_point() / 2.fct(),
146                0.rad(),
147                &[
148                    RenderGradientStop {
149                        color: colors[0],
150                        offset: 0.0,
151                    },
152                    RenderGradientStop {
153                        color: colors[0],
154                        offset: 0.25,
155                    },
156                    RenderGradientStop {
157                        color: colors[1],
158                        offset: 0.25,
159                    },
160                    RenderGradientStop {
161                        color: colors[1],
162                        offset: 0.5,
163                    },
164                    RenderGradientStop {
165                        color: colors[0],
166                        offset: 0.5,
167                    },
168                    RenderGradientStop {
169                        color: colors[0],
170                        offset: 0.75,
171                    },
172                    RenderGradientStop {
173                        color: colors[1],
174                        offset: 0.75,
175                    },
176                    RenderGradientStop {
177                        color: colors[1],
178                        offset: 1.0,
179                    },
180                ],
181                RenderExtendMode::Repeat,
182                tile_origin,
183                tile_size,
184                PxSize::zero(),
185            );
186        }
187        _ => {}
188    })
189}