1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#![doc(html_favicon_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo-icon.png")]
#![doc(html_logo_url = "https://raw.githubusercontent.com/zng-ui/zng/main/examples/image/res/zng-logo.png")]
//!
//! Checkerboard widget, properties and nodes.
//!
//! # Crate
//!
#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
#![warn(unused_extern_crates)]
#![warn(missing_docs)]

use std::ops;

use zng_color::COLOR_SCHEME_VAR;
use zng_wgt::prelude::{
    gradient::{RenderExtendMode, RenderGradientStop},
    *,
};

/// A checkerboard visual.
///
/// This widget draws a checkerboard pattern, with configurable dimensions and colors.
#[widget($crate::Checkerboard)]
pub struct Checkerboard(WidgetBase);
impl Checkerboard {
    fn widget_intrinsic(&mut self) {
        self.widget_builder().push_build_action(|wgt| wgt.set_child(self::node()));
    }
}

/// Checker board colors.
///
/// See [`colors`](fn@colors) for more details.
#[derive(Debug, Clone, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Colors(pub [LightDark; 2]);
impl ops::Deref for Colors {
    type Target = [LightDark; 2];

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
impl_from_and_into_var! {
    fn from<C: Into<LightDark>>([c0, c1]: [C; 2]) -> Colors {
        Colors([c0.into(), c1.into()])
    }
    fn from<C0: Into<LightDark>, C1: Into<LightDark>>((c0, c1): (C0, C1)) -> Colors {
        Colors([c0.into(), c1.into()])
    }
}

context_var! {
    /// The checkerboard colors.
    pub static COLORS_VAR: Colors = [
        light_dark(rgb(202, 202, 204), rgb(20, 20, 20)),
        light_dark(rgb(253, 253, 253), rgb(40, 40, 40)),
    ];

    /// Offset applied to the checkerboard pattern.
    ///
    /// Default is no offset, `0`.
    pub static ORIGIN_VAR: Point = Point::zero();

    /// The size of one color rectangle in the checkerboard.
    ///
    /// Default is `10`.
    pub static SIZE_VAR: Size = 10;
}

/// Set both checkerboard colors.
///
/// The values are the interchanging colors for a given color scheme, for example in the dark
/// color scheme the `(colors[0].dark, colors[1].dark)` colors are used.
///
/// This property sets [`COLORS_VAR`] for all inner checkerboard widgets.
#[property(CONTEXT, default(COLORS_VAR), widget_impl(Checkerboard))]
pub fn colors(child: impl UiNode, colors: impl IntoVar<Colors>) -> impl UiNode {
    with_context_var(child, COLORS_VAR, colors)
}

/// Set the size of a checkerboard color rectangle.
///
/// This property sets the [`SIZE_VAR`] for all inner checkerboard widgets.
#[property(CONTEXT, default(SIZE_VAR), widget_impl(Checkerboard))]
pub fn cb_size(child: impl UiNode, size: impl IntoVar<Size>) -> impl UiNode {
    with_context_var(child, SIZE_VAR, size)
}

/// Sets the offset of the checkerboard pattern.
///
/// Relative values are resolved in the context of a [`cb_size`](fn@cb_size).
///
/// This property sets the [`ORIGIN_VAR`] for all inner checkerboard widgets.
#[property(CONTEXT, default(ORIGIN_VAR), widget_impl(Checkerboard))]
pub fn cb_origin(child: impl UiNode, offset: impl IntoVar<Point>) -> impl UiNode {
    with_context_var(child, ORIGIN_VAR, offset)
}

/// Checkerboard node.
///
/// The node is configured by the contextual variables defined in the widget.
pub fn node() -> impl UiNode {
    let mut render_size = PxSize::zero();
    let mut tile_origin = PxPoint::zero();
    let mut tile_size = PxSize::zero();

    match_node_leaf(move |op| match op {
        UiNodeOp::Init => {
            WIDGET
                .sub_var_render(&COLORS_VAR)
                .sub_var_render(&COLOR_SCHEME_VAR)
                .sub_var_layout(&SIZE_VAR)
                .sub_var_layout(&ORIGIN_VAR);
        }
        UiNodeOp::Measure { desired_size, .. } => {
            *desired_size = LAYOUT.constraints().fill_size();
        }
        UiNodeOp::Layout { final_size, .. } => {
            *final_size = LAYOUT.constraints().fill_size();
            if *final_size != render_size {
                render_size = *final_size;
                WIDGET.render();
            }

            let mut ts = SIZE_VAR.layout_dft(PxSize::splat(Px(4)));
            let to = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(ts), || ORIGIN_VAR.layout());

            // each gradient tile has 4 color rectangles.
            ts *= 2.fct();

            if tile_origin != to || tile_size != ts {
                tile_origin = to;
                tile_size = ts;

                WIDGET.render();
            }
        }
        UiNodeOp::Render { frame } => {
            let [c0, c1] = COLORS_VAR.get().0;
            let sch = COLOR_SCHEME_VAR.get();
            let colors = [c0[sch], c1[sch]];

            frame.push_conic_gradient(
                PxRect::from_size(render_size),
                tile_size.to_vector().to_point() / 2.fct(),
                0.rad(),
                &[
                    RenderGradientStop {
                        color: colors[0],
                        offset: 0.0,
                    },
                    RenderGradientStop {
                        color: colors[0],
                        offset: 0.25,
                    },
                    RenderGradientStop {
                        color: colors[1],
                        offset: 0.25,
                    },
                    RenderGradientStop {
                        color: colors[1],
                        offset: 0.5,
                    },
                    RenderGradientStop {
                        color: colors[0],
                        offset: 0.5,
                    },
                    RenderGradientStop {
                        color: colors[0],
                        offset: 0.75,
                    },
                    RenderGradientStop {
                        color: colors[1],
                        offset: 0.75,
                    },
                    RenderGradientStop {
                        color: colors[1],
                        offset: 1.0,
                    },
                ],
                RenderExtendMode::Repeat,
                tile_origin,
                tile_size,
                PxSize::zero(),
            );
        }
        _ => {}
    })
}