Skip to main content

ferroui_base/rendering/composition/drawing/
composition_painted_brush.rs

1//! Composition brushes that the compositor paints itself (effect, backdrop,
2//! surface, nine-grid and mask brushes, and the XAML brushes over them) as
3//! the fill or the stroke of what render data draws: an element whose
4//! background is such a brush (winui-materials.md).
5
6use crate::media::immutable::{ImmutableDashStyle, ImmutablePen, ImmutableSolidColorBrush};
7use crate::media::imaging::BitmapBlendingMode;
8use crate::media::{BoxShadows, Colors, IBrush, IImmutableBrush, IPen, ITransform};
9use crate::platform::{IDrawingContextImpl, IGeometryImpl, IGlyphRunImpl};
10use crate::rendering::composition::brushes::paint_composition_brush;
11use crate::rendering::composition::server::IServerObject;
12use crate::{Point, Rect, RelativePoint, RoundedRect};
13use std::any::Any;
14use std::rc::Rc;
15
16/// The brush of the render thread that stands for a composition brush the
17/// drawing contexts cannot draw: what `as_brush` of such a server brush
18/// returns. The replay of render data fills and strokes with it by painting
19/// the brush ([`paint_composition_brush`]) inside the shape; a drawing
20/// context that is handed one directly draws nothing with it.
21pub struct CompositionPaintedBrush {
22    object: Rc<dyn IServerObject>,
23}
24
25/// What a painted brush fills or strokes.
26#[derive(Clone, Copy)]
27pub(crate) enum PaintedShape<'a> {
28    Rectangle(RoundedRect),
29    Ellipse(Rect),
30    Geometry(&'a dyn IGeometryImpl),
31    Line(Point, Point),
32    GlyphRun(&'a dyn IGlyphRunImpl),
33}
34
35impl PaintedShape<'_> {
36    /// The area the shape covers when it is filled.
37    fn bounds(&self) -> Rect {
38        match *self {
39            PaintedShape::Rectangle(rect) => rect.rect,
40            PaintedShape::Ellipse(rect) => rect,
41            PaintedShape::Geometry(geometry) => geometry.bounds(),
42            PaintedShape::Line(p1, p2) => Rect::from_points(Point::new(p1.x.min(p2.x), p1.y.min(p2.y)), Point::new(p1.x.max(p2.x), p1.y.max(p2.y))),
43            PaintedShape::GlyphRun(glyph_run) => glyph_run.bounds(),
44        }
45    }
46
47    /// Draws the shape with `brush` and `pen`.
48    fn draw(&self, canvas: &mut dyn IDrawingContextImpl, brush: Option<&dyn IBrush>, pen: Option<&dyn IPen>) {
49        match *self {
50            PaintedShape::Rectangle(rect) => canvas.draw_rectangle(brush, pen, rect, &BoxShadows::default()),
51            PaintedShape::Ellipse(rect) => canvas.draw_ellipse(brush, pen, rect),
52            PaintedShape::Geometry(geometry) => canvas.draw_geometry(brush, pen, geometry),
53            PaintedShape::Line(p1, p2) => canvas.draw_line(pen, p1, p2),
54            PaintedShape::GlyphRun(glyph_run) => {
55                if let Some(brush) = brush {
56                    canvas.draw_glyph_run(Some(brush), glyph_run);
57                }
58            }
59        }
60    }
61}
62
63/// An opaque pen of the form of `pen`: the coverage of its stroke.
64fn coverage_pen(pen: &dyn IPen) -> ImmutablePen {
65    let white: Rc<dyn IImmutableBrush> = Rc::new(ImmutableSolidColorBrush::new(Colors::WHITE));
66    let dash_style = pen
67        .dash_style()
68        .map(|dash_style| Rc::new(ImmutableDashStyle::new(dash_style.dashes().as_deref(), dash_style.offset())));
69    ImmutablePen::new(Some(white), pen.thickness(), dash_style, pen.line_cap(), pen.line_join(), pen.miter_limit())
70}
71
72impl CompositionPaintedBrush {
73    /// The brush for the server-side composition brush `object`.
74    pub fn new(object: Rc<dyn IServerObject>) -> CompositionPaintedBrush {
75        CompositionPaintedBrush { object }
76    }
77
78    /// The server-side brush.
79    pub fn object(&self) -> &Rc<dyn IServerObject> {
80        &self.object
81    }
82
83    /// The painted brush `brush` is, if it is one.
84    pub fn of(brush: Option<&dyn IBrush>) -> Option<&CompositionPaintedBrush> {
85        brush?.as_any().downcast_ref::<CompositionPaintedBrush>()
86    }
87
88    /// Paints the brush over `rect`.
89    pub fn paint(&self, canvas: &mut dyn IDrawingContextImpl, rect: Rect) {
90        paint_composition_brush(canvas, &self.object, rect);
91    }
92
93    /// Fills `shape` with the brush: inside a clip of the shape for a
94    /// rectangle, an ellipse and a geometry; inside the coverage of the
95    /// shape (a destination-in layer) for a glyph run.
96    pub(crate) fn fill(&self, canvas: &mut dyn IDrawingContextImpl, shape: PaintedShape<'_>) {
97        match shape {
98            PaintedShape::Rectangle(rect) => {
99                canvas.push_clip_rounded(rect);
100                self.paint(canvas, rect.rect);
101                canvas.pop_clip();
102            }
103            PaintedShape::Ellipse(rect) => {
104                canvas.push_clip_rounded(RoundedRect::from_radius_xy(rect, rect.width / 2.0, rect.height / 2.0));
105                self.paint(canvas, rect);
106                canvas.pop_clip();
107            }
108            PaintedShape::Geometry(geometry) => {
109                canvas.push_geometry_clip(geometry);
110                self.paint(canvas, geometry.bounds());
111                canvas.pop_geometry_clip();
112            }
113            PaintedShape::Line(..) => {}
114            PaintedShape::GlyphRun(_) => self.paint_through_coverage(canvas, shape, shape.bounds(), None),
115        }
116    }
117
118    /// Strokes `shape` with `pen`, whose brush is this one: the brush is
119    /// painted over the bounds of the stroke inside the coverage of the
120    /// stroke.
121    pub(crate) fn stroke(&self, canvas: &mut dyn IDrawingContextImpl, pen: &dyn IPen, shape: PaintedShape<'_>) {
122        let reach = pen.thickness().max(0.0) + pen.miter_limit().max(1.0) * pen.thickness().max(0.0) / 2.0;
123        let bounds = shape.bounds().inflate(reach);
124        let coverage = coverage_pen(pen);
125        self.paint_through_coverage(canvas, shape, bounds, Some(&coverage));
126    }
127
128    /// Paints the brush over `bounds` in a layer, then keeps what the shape
129    /// covers (drawn with an opaque brush, or with `pen`) with
130    /// destination-in. Without the layers of the drawing context nothing is
131    /// drawn.
132    fn paint_through_coverage(
133        &self,
134        canvas: &mut dyn IDrawingContextImpl,
135        shape: PaintedShape<'_>,
136        bounds: Rect,
137        pen: Option<&ImmutablePen>,
138    ) {
139        if canvas.as_drawing_context_impl_with_layer_effects().is_none() || bounds.width <= 0.0 || bounds.height <= 0.0 {
140            return;
141        }
142        canvas.push_layer(bounds);
143        self.paint(canvas, bounds);
144        if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
145            layers.push_blend_layer(bounds, BitmapBlendingMode::DestinationIn);
146        }
147        let white = ImmutableSolidColorBrush::new(Colors::WHITE);
148        match pen {
149            Some(pen) => shape.draw(canvas, None, Some(pen)),
150            None => shape.draw(canvas, Some(&white), None),
151        }
152        if let Some(layers) = canvas.as_drawing_context_impl_with_layer_effects() {
153            layers.pop_blend_layer();
154        }
155        canvas.pop_layer();
156    }
157}
158
159impl IBrush for CompositionPaintedBrush {
160    fn opacity(&self) -> f64 {
161        1.0
162    }
163
164    fn transform(&self) -> Option<Rc<dyn ITransform>> {
165        None
166    }
167
168    fn transform_origin(&self) -> RelativePoint {
169        RelativePoint::default()
170    }
171
172    fn relative_transform(&self) -> Option<Rc<dyn ITransform>> {
173        None
174    }
175
176    fn as_any(&self) -> &dyn Any {
177        self
178    }
179}
180
181/// Draws what render data draws with a brush and a pen, painting the brush
182/// or the brush of the pen when it is a [`CompositionPaintedBrush`].
183pub(crate) fn draw_with_painted_brushes(
184    canvas: &mut dyn IDrawingContextImpl,
185    brush: Option<&dyn IBrush>,
186    pen: Option<&dyn IPen>,
187    shape: PaintedShape<'_>,
188    box_shadows: Option<&BoxShadows>,
189) -> bool {
190    let painted_fill = CompositionPaintedBrush::of(brush);
191    let pen_brush = pen.and_then(|pen| pen.brush());
192    let painted_stroke = CompositionPaintedBrush::of(pen_brush.as_deref());
193    if painted_fill.is_none() && painted_stroke.is_none() {
194        return false;
195    }
196    // The shadows first, under the fill.
197    if let (Some(box_shadows), PaintedShape::Rectangle(rect)) = (box_shadows, shape) {
198        if box_shadows.count() > 0 {
199            canvas.draw_rectangle(None, None, rect, box_shadows);
200        }
201    }
202    match painted_fill {
203        Some(painted) => painted.fill(canvas, shape),
204        None => {
205            if brush.is_some() {
206                shape.draw(canvas, brush, None);
207            }
208        }
209    }
210    if let Some(pen) = pen {
211        match painted_stroke {
212            Some(painted) => painted.stroke(canvas, pen, shape),
213            None => shape.draw(canvas, None, Some(pen)),
214        }
215    }
216    true
217}
218
219/// The painted brush of a server-side composition brush that the drawing
220/// contexts cannot draw (effect, backdrop, host backdrop, surface,
221/// nine-grid, mask and XAML brushes); `None` for any other object.
222pub(crate) fn painted_brush_of(object: &Rc<dyn IServerObject>) -> Option<Rc<dyn IBrush>> {
223    is_painted_composition_brush(&**object).then(|| Rc::new(CompositionPaintedBrush::new(object.clone())) as Rc<dyn IBrush>)
224}
225
226/// Whether `object` is a composition brush that [`paint_composition_brush`]
227/// paints itself rather than handing it to the drawing context.
228pub(crate) fn is_painted_composition_brush(object: &dyn IServerObject) -> bool {
229    use crate::rendering::composition::brushes::{
230        ServerCompositionBackdropBrush, ServerCompositionEffectBrush, ServerCompositionMaskBrush,
231        ServerCompositionNineGridBrush, ServerCompositionSurfaceBrush,
232    };
233    let any = object.as_any();
234    any.is::<ServerCompositionSurfaceBrush>()
235        || any.is::<ServerCompositionNineGridBrush>()
236        || any.is::<ServerCompositionMaskBrush>()
237        || any.is::<ServerCompositionEffectBrush>()
238        || any.is::<ServerCompositionBackdropBrush>()
239        || any.is::<crate::rendering::composition::brushes::ServerCompositionHostBackdropBrush>()
240        || any.is::<crate::rendering::composition::brushes::ServerXamlCompositionBrush>()
241}