Skip to main content

drawnui/
types.rs

1//! Value types with the DrawnUI names. Units are points unless a name says pixels.
2
3use skia_safe::{BlendMode, Color, Point, TileMode};
4
5/// Where a control sits inside the box its parent gives it.
6#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
7pub enum LayoutOptions {
8    #[default]
9    Start,
10    Center,
11    End,
12    Fill,
13}
14
15#[derive(Clone, Copy, PartialEq, Debug, Default)]
16pub struct Thickness {
17    pub left: f32,
18    pub top: f32,
19    pub right: f32,
20    pub bottom: f32,
21}
22
23impl Thickness {
24    pub const ZERO: Thickness = Thickness { left: 0.0, top: 0.0, right: 0.0, bottom: 0.0 };
25
26    pub const fn new(left: f32, top: f32, right: f32, bottom: f32) -> Self {
27        Self { left, top, right, bottom }
28    }
29    pub const fn uniform(all: f32) -> Self {
30        Self::new(all, all, all, all)
31    }
32    pub fn horizontal(&self) -> f32 {
33        self.left + self.right
34    }
35    pub fn vertical(&self) -> f32 {
36        self.top + self.bottom
37    }
38    /// The larger of the two on every side.
39    pub fn max(self, other: Thickness) -> Thickness {
40        let (a, b) = (self, other);
41        Thickness::new(a.left.max(b.left), a.top.max(b.top), a.right.max(b.right), a.bottom.max(b.bottom))
42    }
43}
44
45/// Where the canvas draws (DrawnUI `Canvas.RenderingMode`; Accelerated by default as in
46/// DrawnUi.React, C# defaults to Default).
47#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
48pub enum RenderingModeType {
49    /// The CPU: each frame is drawn into memory, then shown. Caches are CPU bitmaps. A browser
50    /// that refuses WebGL2 draws this way whatever was asked.
51    Default,
52    /// The GPU: WebGL2, OpenGL or Metal.
53    #[default]
54    Accelerated,
55}
56
57/// What the canvas does with gestures the page could take too (DrawnUI `Canvas.Gestures`). The
58/// browser host acts on it; the native hosts own their window and ignore it.
59#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
60pub enum GesturesMode {
61    /// The canvas takes its gestures; the page keeps what the app does not use (the wheel) and its
62    /// own CSS decides touch panning.
63    #[default]
64    Enabled,
65    /// The canvas owns every touch: no page scroll, bounce, pull-down or text selection starts on
66    /// it (DrawnUi.Web `applyGestureStyle` with lock). For full-screen apps and games.
67    Lock,
68}
69
70/// The GPU API the canvas draws with (DrawnUi.Rust; DrawnUI picks per platform itself).
71#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
72pub enum GpuBackend {
73    /// Vulkan on Android, OpenGL ES where Vulkan cannot be made; OpenGL on Windows and Linux,
74    /// Metal on Apple platforms, WebGL2 in the browser.
75    #[default]
76    Auto,
77    /// OpenGL (ES on Android) where the platform has a choice.
78    OpenGl,
79    /// Vulkan where the platform has it (Android), else as `Auto`.
80    Vulkan,
81}
82
83/// What a control keeps between frames instead of painting again (DrawnUI SkiaCacheType).
84#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
85#[allow(clippy::upper_case_acronyms)]
86pub enum CacheType {
87    #[default]
88    None,
89    /// Recorded draw commands, replayed each frame.
90    Operations,
91    /// Recorded draw commands over the whole area the canvas shows (its clip), not only the
92    /// control's rect: for a control that paints outside its rect. Recorded again when the size
93    /// of that area changes; a cache that must move inside a scroll belongs on the scrolled parent.
94    OperationsFull,
95    /// Offscreen image on the window's GPU context, blitted each frame (DrawnUI's GPU cache; its
96    /// Image cache is a CPU bitmap).
97    Image,
98    /// DrawnUI GPU: the same as `Image`, which is on the GPU already.
99    GPU,
100    /// A CPU bitmap made off the frame thread: the last one is drawn while the next one is made
101    /// (the desktop; the browser makes it in the frame, as `Image`).
102    ImageDoubleBuffered,
103    /// An Image cache whose surface is kept: when only some children changed, they and the
104    /// siblings they overlap are drawn again into it, the rest stays.
105    ImageComposite,
106    /// DrawnUI ImageCompositeGPU: the same as `ImageComposite`, which is on the GPU already.
107    ImageCompositeGPU,
108}
109
110impl CacheType {
111    /// The cache a control gets: the GPU names are the caches that are on the GPU already.
112    pub(crate) fn resolved(self) -> Self {
113        match self {
114            CacheType::GPU => CacheType::Image,
115            CacheType::ImageCompositeGPU => CacheType::ImageComposite,
116            other => other,
117        }
118    }
119}
120
121/// Which gestures a control lets through to its children.
122#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
123pub enum LockTouch {
124    #[default]
125    Disabled,
126    Enabled,
127    PassNone,
128    PassTap,
129    PassTapAndLongPress,
130}
131
132/// What a property change invalidates.
133#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
134pub struct Dirty(pub(crate) u8);
135
136impl Dirty {
137    pub const NONE: Dirty = Dirty(0);
138    /// Size may change: the control and its ancestors measure again, caches are dropped.
139    pub const MEASURE: Dirty = Dirty(1);
140    /// Own pixels change: own cache and ancestor caches are recorded again.
141    pub const DRAW: Dirty = Dirty(2);
142    /// Position, transform or opacity: own cache stays, ancestors composite again.
143    pub const REPAINT: Dirty = Dirty(4);
144    /// The control's `on_props_changed` runs before the next layout.
145    pub const APPLY: Dirty = Dirty(8);
146    pub const MEASURE_APPLY: Dirty = Dirty(1 | 8);
147    pub const DRAW_APPLY: Dirty = Dirty(2 | 8);
148
149    pub fn contains(self, other: Dirty) -> bool {
150        self.0 & other.0 == other.0 && other.0 != 0
151    }
152    pub fn is_empty(self) -> bool {
153        self.0 == 0
154    }
155}
156
157impl std::ops::BitOrAssign for Dirty {
158    fn bitor_assign(&mut self, rhs: Dirty) {
159        self.0 |= rhs.0
160    }
161}
162
163/// Conversion accepted by property builders and setters, so `16`, `16.0` and `"text"` all work.
164pub trait IntoProp<T> {
165    fn into_prop(self) -> T;
166}
167
168impl<T> IntoProp<T> for T {
169    fn into_prop(self) -> T {
170        self
171    }
172}
173impl IntoProp<f32> for i32 {
174    fn into_prop(self) -> f32 {
175        self as f32
176    }
177}
178impl IntoProp<f32> for f64 {
179    fn into_prop(self) -> f32 {
180        self as f32
181    }
182}
183impl IntoProp<String> for &str {
184    fn into_prop(self) -> String {
185        self.to_owned()
186    }
187}
188impl IntoProp<Option<Color>> for Color {
189    fn into_prop(self) -> Option<Color> {
190        Some(self)
191    }
192}
193impl IntoProp<Option<char>> for char {
194    fn into_prop(self) -> Option<char> {
195        Some(self)
196    }
197}
198impl IntoProp<Option<bool>> for bool {
199    fn into_prop(self) -> Option<bool> {
200        Some(self)
201    }
202}
203impl IntoProp<Thickness> for f32 {
204    fn into_prop(self) -> Thickness {
205        Thickness::uniform(self)
206    }
207}
208impl IntoProp<Thickness> for i32 {
209    fn into_prop(self) -> Thickness {
210        Thickness::uniform(self as f32)
211    }
212}
213impl IntoProp<Thickness> for (f32, f32) {
214    /// (horizontal, vertical)
215    fn into_prop(self) -> Thickness {
216        Thickness::new(self.0, self.1, self.0, self.1)
217    }
218}
219impl IntoProp<Thickness> for (i32, i32) {
220    fn into_prop(self) -> Thickness {
221        (self.0 as f32, self.1 as f32).into_prop()
222    }
223}
224impl IntoProp<Thickness> for (f32, f32, f32, f32) {
225    /// (left, top, right, bottom)
226    fn into_prop(self) -> Thickness {
227        Thickness::new(self.0, self.1, self.2, self.3)
228    }
229}
230impl IntoProp<Thickness> for (i32, i32, i32, i32) {
231    fn into_prop(self) -> Thickness {
232        Thickness::new(self.0 as f32, self.1 as f32, self.2 as f32, self.3 as f32)
233    }
234}
235
236// ---------------------------------------------------------------- shapes, gradients, shadows
237
238/// Corner radii in points, in the order of the MAUI constructor.
239#[derive(Clone, Copy, PartialEq, Debug, Default)]
240pub struct CornerRadius {
241    pub top_left: f32,
242    pub top_right: f32,
243    pub bottom_left: f32,
244    pub bottom_right: f32,
245}
246
247impl CornerRadius {
248    pub const fn new(top_left: f32, top_right: f32, bottom_left: f32, bottom_right: f32) -> Self {
249        Self { top_left, top_right, bottom_left, bottom_right }
250    }
251    pub const fn uniform(all: f32) -> Self {
252        Self::new(all, all, all, all)
253    }
254    pub fn is_zero(&self) -> bool {
255        *self == Self::default()
256    }
257}
258
259impl IntoProp<CornerRadius> for f32 {
260    fn into_prop(self) -> CornerRadius {
261        CornerRadius::uniform(self)
262    }
263}
264impl IntoProp<CornerRadius> for i32 {
265    fn into_prop(self) -> CornerRadius {
266        CornerRadius::uniform(self as f32)
267    }
268}
269impl IntoProp<CornerRadius> for (f32, f32, f32, f32) {
270    /// (top left, top right, bottom left, bottom right)
271    fn into_prop(self) -> CornerRadius {
272        CornerRadius::new(self.0, self.1, self.2, self.3)
273    }
274}
275impl IntoProp<CornerRadius> for (i32, i32, i32, i32) {
276    fn into_prop(self) -> CornerRadius {
277        CornerRadius::new(self.0 as f32, self.1 as f32, self.2 as f32, self.3 as f32)
278    }
279}
280
281/// Polygon and Line points, as ratios of the shape's box.
282impl IntoProp<Vec<Point>> for Vec<(f32, f32)> {
283    fn into_prop(self) -> Vec<Point> {
284        self.into_iter().map(Point::from).collect()
285    }
286}
287
288#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
289pub enum GradientType {
290    None,
291    #[default]
292    Linear,
293    /// Radial, radius = half of the smaller side.
294    Circular,
295    /// Radial, stretched to both sides.
296    Oval,
297    /// Around the center, between the control's `value1` and `value1 + value2` degrees.
298    Sweep,
299    /// Drawn as Linear, as upstream.
300    Conical,
301}
302
303/// DrawnUI SkiaGradient. Start and end are ratios of the rect the gradient fills.
304#[derive(Clone, PartialEq, Debug)]
305pub struct SkiaGradient {
306    pub gradient_type: GradientType,
307    pub colors: Vec<Color>,
308    /// 0..1 per color; used only when there is one per color, else the colors are spread evenly.
309    pub color_positions: Vec<f32>,
310    /// Linear: the start point. Circular and Oval: the center.
311    pub start_x_ratio: f32,
312    pub start_y_ratio: f32,
313    pub end_x_ratio: f32,
314    pub end_y_ratio: f32,
315    pub tile_mode: TileMode,
316    /// Below 1 darkens the colors, above 1 lightens them.
317    pub light: f32,
318    /// Multiplies the alpha of every color.
319    pub opacity: f32,
320    pub blend_mode: BlendMode,
321}
322
323impl Default for SkiaGradient {
324    fn default() -> Self {
325        Self {
326            gradient_type: GradientType::Linear,
327            colors: Vec::new(),
328            color_positions: Vec::new(),
329            start_x_ratio: 0.0,
330            start_y_ratio: 0.0,
331            end_x_ratio: 0.0,
332            end_y_ratio: 1.0,
333            tile_mode: TileMode::Clamp,
334            light: 1.0,
335            opacity: 1.0,
336            blend_mode: BlendMode::SrcOver,
337        }
338    }
339}
340
341impl SkiaGradient {
342    /// Top to bottom for Linear; set the ratios or `angle` for another direction.
343    pub fn new(gradient_type: GradientType, colors: impl Into<Vec<Color>>) -> Self {
344        Self { gradient_type, colors: colors.into(), ..Self::default() }
345    }
346
347    /// Direction of a Linear gradient in degrees: 0 = top to bottom, 90 = left to right, 180 =
348    /// bottom to top, 270 = right to left. Sets the start and end ratios (DrawnUI
349    /// LinearGradientAngleToPoints).
350    pub fn angle(mut self, degrees: f32) -> Self {
351        let mut direction = degrees - 90.0;
352        if direction < 0.0 {
353            direction += 360.0;
354        }
355        let angle = direction.min(360.0) % 360.0;
356        // A direction that points backwards on an axis starts at 0 on it.
357        let ratio = |v: f32| if v <= f32::EPSILON { 0.0 } else { v };
358        let (start, end) = ((180.0 - angle).to_radians(), (360.0 - angle).to_radians());
359        (self.start_x_ratio, self.start_y_ratio) = (ratio(start.cos()), ratio(start.sin()));
360        (self.end_x_ratio, self.end_y_ratio) = (ratio(end.cos()), ratio(end.sin()));
361        self
362    }
363}
364
365/// Fluent setters named after the fields: `SkiaShadow::new(color).y(4).blur(6)`.
366macro_rules! fluent {
367    ($ty:ident { $($name:ident: $field:ty),* $(,)? }) => {
368        impl $ty {
369            $(pub fn $name(mut self, v: impl IntoProp<$field>) -> Self {
370                self.$name = v.into_prop();
371                self
372            })*
373        }
374    };
375}
376
377fluent!(SkiaGradient {
378    gradient_type: GradientType,
379    colors: Vec<Color>,
380    color_positions: Vec<f32>,
381    start_x_ratio: f32,
382    start_y_ratio: f32,
383    end_x_ratio: f32,
384    end_y_ratio: f32,
385    tile_mode: TileMode,
386    light: f32,
387    opacity: f32,
388    blend_mode: BlendMode,
389});
390
391impl IntoProp<Option<Box<SkiaGradient>>> for SkiaGradient {
392    fn into_prop(self) -> Option<Box<SkiaGradient>> {
393        Some(Box::new(self))
394    }
395}
396
397/// DrawnUI SkiaShadow: a blurred copy of the shape behind it. Offsets and blur are points.
398#[derive(Clone, Copy, PartialEq, Debug)]
399pub struct SkiaShadow {
400    pub x: f32,
401    pub y: f32,
402    /// The blur sigma.
403    pub blur: f32,
404    /// Alpha of the shadow when `color` is fully opaque; a color with its own alpha keeps it.
405    pub opacity: f32,
406    pub color: Color,
407    /// Draws the shadow without the shape.
408    pub shadow_only: bool,
409}
410
411impl Default for SkiaShadow {
412    fn default() -> Self {
413        Self { x: 2.0, y: 2.0, blur: 5.0, opacity: 0.5, color: Color::TRANSPARENT, shadow_only: false }
414    }
415}
416
417impl SkiaShadow {
418    /// A shadow of this color with the upstream defaults: 2 points right and down, blur 5,
419    /// opacity 0.5.
420    pub fn new(color: Color) -> Self {
421        Self { color, ..Self::default() }
422    }
423}
424
425fluent!(SkiaShadow { x: f32, y: f32, blur: f32, opacity: f32, color: Color, shadow_only: bool });
426
427impl IntoProp<Vec<SkiaShadow>> for SkiaShadow {
428    fn into_prop(self) -> Vec<SkiaShadow> {
429        vec![self]
430    }
431}