Skip to main content

ferroui_base/rendering/composition/brushes/
composition_gradient_winui.rs

1//! The WinUI members of the gradient brushes (phase 2, not from upstream):
2//! the brush transform, `ExtendMode`, `InterpolationSpace`, `MappingMode`,
3//! `ColorStops`, the `Vector2` points of the linear brush and the ellipse of
4//! the radial brush, mapped onto the members of the upstream brushes.
5
6use super::composition_brush::{CompositionBrush, CompositionBrushKind};
7use super::{CompositionGradientBrush, CompositionLinearGradientBrush, CompositionRadialGradientBrush};
8use crate::collections::FerroList;
9use crate::media::immutable::ImmutableTransform;
10use crate::media::{GradientSpreadMethod, IGradientStop, ITransform};
11use crate::rendering::composition::animations::CompositionColorSpace;
12use crate::rendering::composition::{CompositionGradientExtendMode, CompositionMappingMode};
13use crate::{Matrix, RelativePoint, RelativeScalar, RelativeUnit, Vector};
14use std::rc::Rc;
15
16/// The unit the WinUI members of a gradient brush set its points in.
17fn unit_of(mode: CompositionMappingMode) -> RelativeUnit {
18    match mode {
19        CompositionMappingMode::Absolute => RelativeUnit::Absolute,
20        CompositionMappingMode::Relative => RelativeUnit::Relative,
21    }
22}
23
24fn point_of(value: Vector, unit: RelativeUnit) -> RelativePoint {
25    RelativePoint::new(value.x, value.y, unit)
26}
27
28fn vector_of(point: RelativePoint) -> Vector {
29    Vector::new(point.point.x, point.point.y)
30}
31
32/// Runs `$body` with `$props` bound to the generated properties of the
33/// concrete gradient class of `$brush` (a `&CompositionBrush`).
34macro_rules! with_gradient_props {
35    ($brush:expr, |$props:ident| $body:expr) => {
36        match &$brush.kind {
37            CompositionBrushKind::LinearGradient(_, $props) => $body,
38            CompositionBrushKind::RadialGradient(_, $props) => $body,
39            CompositionBrushKind::ConicGradient(_, $props) => $body,
40            _ => unreachable!("a gradient brush handle wraps a gradient brush"),
41        }
42    };
43}
44
45/// The value members of the brush transform, animatable under their WinUI
46/// names.
47macro_rules! gradient_transform_members {
48    ($($(#[$meta:meta])* $get:ident / $set:ident : $ty:ty;)*) => {
49        impl CompositionGradientBrush {$(
50            $(#[$meta])*
51            pub fn $get(&self) -> $ty {
52                let brush: &CompositionBrush = self;
53                with_gradient_props!(brush, |props| props.$get())
54            }
55
56            pub fn $set(&self, value: $ty) {
57                let brush: &CompositionBrush = self;
58                with_gradient_props!(brush, |props| props.$set(brush, value))
59            }
60        )*}
61    };
62}
63
64gradient_transform_members! {
65    /// The point of the gradient, relative to the size of the painted area,
66    /// that is placed at `offset` (animatable as `AnchorPoint`).
67    anchor_point / set_anchor_point: Vector;
68    /// The point the gradient is scaled and rotated about, in the
69    /// coordinates of the painted area (animatable as `CenterPoint`).
70    center_point / set_center_point: Vector;
71    /// The offset of the gradient (animatable as `Offset`).
72    offset / set_offset: Vector;
73    /// The rotation of the gradient about its centre point, in radians
74    /// (animatable as `RotationAngle`).
75    rotation_angle / set_rotation_angle: f32;
76    /// The scale of the gradient about its centre point (default `(1, 1)`;
77    /// animatable as `Scale`).
78    scale / set_scale: Vector;
79    /// The transform matrix of the gradient (WinUI `Matrix3x2`; animatable
80    /// as `TransformMatrix`).
81    transform_matrix / set_transform_matrix: Matrix;
82}
83
84impl CompositionGradientBrush {
85    fn gradient_brush(&self) -> &CompositionBrush {
86        self
87    }
88
89    fn mapping_cell(&self) -> &std::cell::Cell<CompositionMappingMode> {
90        match &self.gradient_brush().kind {
91            CompositionBrushKind::LinearGradient(data, _)
92            | CompositionBrushKind::RadialGradient(data, _)
93            | CompositionBrushKind::ConicGradient(data, _) => &data.mapping_mode,
94            _ => unreachable!("a gradient brush handle wraps a gradient brush"),
95        }
96    }
97
98    fn interpolation_cell(&self) -> &std::cell::Cell<CompositionColorSpace> {
99        match &self.gradient_brush().kind {
100            CompositionBrushKind::LinearGradient(data, _)
101            | CompositionBrushKind::RadialGradient(data, _)
102            | CompositionBrushKind::ConicGradient(data, _) => &data.interpolation_space,
103            _ => unreachable!("a gradient brush handle wraps a gradient brush"),
104        }
105    }
106
107    /// The rotation in degrees: a view of
108    /// [`rotation_angle`](Self::rotation_angle) (not an animation target).
109    pub fn rotation_angle_in_degrees(&self) -> f32 {
110        self.rotation_angle().to_degrees()
111    }
112
113    pub fn set_rotation_angle_in_degrees(&self, value: f32) {
114        self.set_rotation_angle(value.to_radians())
115    }
116
117    /// How the gradient continues outside its stops: a view of
118    /// [`spread_method`](Self::spread_method) (`Clamp` is `Pad`, `Wrap` is
119    /// `Repeat`, `Mirror` is `Reflect`).
120    pub fn extend_mode(&self) -> CompositionGradientExtendMode {
121        match self.spread_method() {
122            GradientSpreadMethod::Pad => CompositionGradientExtendMode::Clamp,
123            GradientSpreadMethod::Repeat => CompositionGradientExtendMode::Wrap,
124            GradientSpreadMethod::Reflect => CompositionGradientExtendMode::Mirror,
125        }
126    }
127
128    pub fn set_extend_mode(&self, value: CompositionGradientExtendMode) {
129        self.set_spread_method(match value {
130            CompositionGradientExtendMode::Clamp => GradientSpreadMethod::Pad,
131            CompositionGradientExtendMode::Wrap => GradientSpreadMethod::Repeat,
132            CompositionGradientExtendMode::Mirror => GradientSpreadMethod::Reflect,
133        })
134    }
135
136    /// The colour space the stops are interpolated in.
137    ///
138    /// Deviation (winui-composition.md): stored only; the drawing contexts
139    /// interpolate in RGB.
140    pub fn interpolation_space(&self) -> CompositionColorSpace {
141        self.interpolation_cell().get()
142    }
143
144    pub fn set_interpolation_space(&self, value: CompositionColorSpace) {
145        self.interpolation_cell().set(value)
146    }
147
148    /// How the points of the WinUI members are measured (default
149    /// `Relative`). Changing it keeps the numbers of the points and changes
150    /// their unit, as WinUI reads the same numbers in the new mode.
151    pub fn mapping_mode(&self) -> CompositionMappingMode {
152        self.mapping_cell().get()
153    }
154
155    pub fn set_mapping_mode(&self, value: CompositionMappingMode) {
156        if self.mapping_cell().replace(value) == value {
157            return;
158        }
159        let unit = unit_of(value);
160        let brush = self.gradient_brush();
161        match &brush.kind {
162            CompositionBrushKind::LinearGradient(_, props) => {
163                props.set_start_point(brush, point_of(vector_of(props.start_point()), unit));
164                props.set_end_point(brush, point_of(vector_of(props.end_point()), unit));
165            }
166            CompositionBrushKind::RadialGradient(_, props) => {
167                props.set_center(brush, point_of(vector_of(props.center()), unit));
168                props.set_gradient_origin(brush, point_of(vector_of(props.gradient_origin()), unit));
169                props.set_radius_x(brush, RelativeScalar::new(props.radius_x().scalar, unit));
170                props.set_radius_y(brush, RelativeScalar::new(props.radius_y().scalar, unit));
171            }
172            CompositionBrushKind::ConicGradient(_, props) => {
173                props.set_center(brush, point_of(vector_of(props.center()), unit));
174            }
175            _ => {}
176        }
177    }
178
179    /// `ColorStops`: the WinUI name of
180    /// [`gradient_stops`](Self::gradient_stops), with the same rule: a change
181    /// of the list itself is shipped when the list is assigned again
182    /// ([`set_color_stops`](Self::set_color_stops)).
183    pub fn color_stops(&self) -> FerroList<Rc<dyn IGradientStop>> {
184        self.gradient_stops()
185    }
186
187    pub fn set_color_stops(&self, value: FerroList<Rc<dyn IGradientStop>>) {
188        self.set_gradient_stops(value)
189    }
190}
191
192impl CompositionLinearGradientBrush {
193    /// `StartPoint` as a WinUI `Vector2`, in the unit of the mapping mode
194    /// (default `(0, 0)`). The upstream member is
195    /// [`start_point`](Self::start_point).
196    ///
197    /// Deviation (winui-composition.md): animations target the upstream
198    /// `StartPoint` (a relative point), not a `Vector2`.
199    pub fn start_point_vector(&self) -> Vector {
200        vector_of(self.start_point())
201    }
202
203    pub fn set_start_point_vector(&self, value: Vector) {
204        self.set_start_point(point_of(value, unit_of(self.mapping_mode())))
205    }
206
207    /// `EndPoint` as a WinUI `Vector2`, in the unit of the mapping mode.
208    ///
209    /// Deviation (winui-composition.md): the default is the one of the
210    /// upstream brush, `(0, 0)`, not the `(1, 0)` of WinUI.
211    pub fn end_point_vector(&self) -> Vector {
212        vector_of(self.end_point())
213    }
214
215    pub fn set_end_point_vector(&self, value: Vector) {
216        self.set_end_point(point_of(value, unit_of(self.mapping_mode())))
217    }
218}
219
220impl CompositionRadialGradientBrush {
221    /// `EllipseCenter`: the centre of the ellipse, the upstream
222    /// [`center`](Self::center), in the unit of the mapping mode.
223    pub fn ellipse_center(&self) -> Vector {
224        vector_of(self.center())
225    }
226
227    /// Sets the centre; the gradient origin moves with it, keeping
228    /// [`gradient_origin_offset`](Self::gradient_origin_offset).
229    pub fn set_ellipse_center(&self, value: Vector) {
230        let offset = self.gradient_origin_offset();
231        let unit = unit_of(self.mapping_mode());
232        self.set_center(point_of(value, unit));
233        self.set_gradient_origin(point_of(value + offset, unit));
234    }
235
236    /// `EllipseRadius`: the radii of the ellipse, the upstream
237    /// [`radius_x`](Self::radius_x) and [`radius_y`](Self::radius_y), in the
238    /// unit of the mapping mode.
239    pub fn ellipse_radius(&self) -> Vector {
240        Vector::new(self.radius_x().scalar, self.radius_y().scalar)
241    }
242
243    pub fn set_ellipse_radius(&self, value: Vector) {
244        let unit = unit_of(self.mapping_mode());
245        self.set_radius_x(RelativeScalar::new(value.x, unit));
246        self.set_radius_y(RelativeScalar::new(value.y, unit));
247    }
248
249    /// `GradientOriginOffset`: where the gradient starts relative to the
250    /// centre of the ellipse; the upstream
251    /// [`gradient_origin`](Self::gradient_origin) is the centre plus this
252    /// offset.
253    pub fn gradient_origin_offset(&self) -> Vector {
254        vector_of(self.gradient_origin()) - vector_of(self.center())
255    }
256
257    pub fn set_gradient_origin_offset(&self, value: Vector) {
258        let unit = unit_of(self.mapping_mode());
259        self.set_gradient_origin(point_of(self.ellipse_center() + value, unit));
260    }
261}
262
263// --- the server side ---------------------------------------------------------------
264
265/// The WinUI brush transform of a server-side composition brush that is a
266/// brush of the drawing context: the drawing context applies it through the
267/// `Transform` and `RelativeTransform` the brush reports.
268pub trait IServerBrushWinUiTransform {
269    /// The brush transform without its anchor point (`CenterPoint`,
270    /// `Offset`, `RotationAngle`, `Scale`, `TransformMatrix`), in the
271    /// coordinates of the painted area; `None` for the identity.
272    fn winui_transform(&self) -> Option<Matrix> {
273        None
274    }
275
276    /// The anchor point, relative to the size of the painted area.
277    fn winui_anchor_point(&self) -> Vector {
278        Vector::default()
279    }
280}
281
282/// The `Transform` of a brush with its WinUI brush transform: the upstream
283/// transform, then the WinUI one.
284///
285/// Deviation (winui-composition.md): both are applied about the upstream
286/// `TransformOrigin` (by default the top left corner of the painted area,
287/// which is where WinUI applies its brush transform).
288pub(crate) fn with_winui_transform(
289    upstream: Option<Rc<dyn ITransform>>,
290    winui: Option<Matrix>,
291) -> Option<Rc<dyn ITransform>> {
292    match (upstream, winui) {
293        (upstream, None) => upstream,
294        (None, Some(winui)) => Some(Rc::new(ImmutableTransform::new(winui))),
295        (Some(upstream), Some(winui)) => Some(Rc::new(ImmutableTransform::new(upstream.value() * winui))),
296    }
297}
298
299/// The `RelativeTransform` of a brush with its WinUI anchor point: the
300/// anchor point is a translation by its negation in the unit square of the
301/// painted area, applied before the absolute transform, as WinUI applies it
302/// first.
303pub(crate) fn with_winui_anchor(upstream: Option<Rc<dyn ITransform>>, anchor: Vector) -> Option<Rc<dyn ITransform>> {
304    if anchor == Vector::default() {
305        return upstream;
306    }
307    let translation = Matrix::create_translation(-anchor.x, -anchor.y);
308    match upstream {
309        None => Some(Rc::new(ImmutableTransform::new(translation))),
310        Some(upstream) => Some(Rc::new(ImmutableTransform::new(upstream.value() * translation))),
311    }
312}