pixel_widgets/
draw.rs

1use crate::layout::{Rectangle, Size};
2use crate::text::Text;
3use smallvec::SmallVec;
4use std::sync::Arc;
5use zerocopy::AsBytes;
6
7/// A high level primitive that can be drawn without any further data.
8#[derive(Clone)]
9pub enum Primitive<'a> {
10    /// Pushes a clipping rectangle on a clipping rectangle stack.
11    /// The topmost clipping rectangle is used by the renderer. When a clipping rectangle is active, only pixels
12    /// inside of the rectangle are actually drawn to the screen. This is useful for scrolling like behaviour.
13    PushClip(Rectangle),
14    /// Pops a clipping rectangle from a clipping rectangle stack. All [`PushClip`s](#variant.PushClip) should have
15    /// a matching `PopClip`.
16    PopClip,
17    /// Move following commands one layer up. Higher layers always draw in front of lower layers.
18    LayerUp,
19    /// Move following commands one layer down. Higher layers always draw in front of lower layers.
20    LayerDown,
21    /// Draw a rectangle filled with a color.
22    DrawRect(Rectangle, Color),
23    /// Draw some text within the bounds of a rectangle.
24    /// See [`Text`](../text/struct.Text.html) for more information.
25    DrawText(Text<'a>, Rectangle),
26    /// Draw a 9 patch spanning the bounds of a rectangle, multiplied by a color.
27    Draw9(Patch, Rectangle, Color),
28    /// Draw an image stretched to the bounds of a rectangle, multiplied by a color.
29    DrawImage(ImageData, Rectangle, Color),
30}
31
32/// A color with red, green, blue and alpha components.
33#[derive(Clone, Copy, Debug)]
34pub struct Color {
35    /// The red component in `[0.0-1.0]` range.
36    pub r: f32,
37    /// The green component in `[0.0-1.0]` range.
38    pub g: f32,
39    /// The blue component in `[0.0-1.0]` range.
40    pub b: f32,
41    /// The alpha component in `[0.0-1.0]` range.
42    pub a: f32,
43}
44
45/// Reference to an image loaded by the [`Ui`](../struct.Ui.html).
46#[derive(Clone, Debug)]
47pub struct ImageData {
48    /// The texture atlas identifier that this image resides in.
49    pub texture: usize,
50    pub(crate) cache_id: Arc<usize>,
51    /// The texcoords within the atlas that the image spans.
52    pub texcoords: Rectangle,
53    /// The physical size in pixels of the image.
54    pub size: Rectangle,
55}
56
57/// 9 patch data on top of an [`Image`](struct.Image.html), which is used to create dynamically stretchable images.
58#[derive(Clone, Debug)]
59pub struct Patch {
60    /// The `Image` this `Patch` operates on.
61    pub image: ImageData,
62    /// Horizontally stretchable regions in the 9 patch image.
63    /// Every element is a pair of begin and end of the stretchable region.
64    /// Defined in relative coordinates: 0.0 is the left side of the image,
65    /// 1.0 is the right side of the image.
66    pub h_stretch: SmallVec<[(f32, f32); 2]>,
67    /// Vertically stretchable regions in the 9 patch image.
68    /// Every element is a pair of begin and end of the stretchable region.
69    /// Defined in relative coordinates: 0.0 is the top side of the image,
70    /// 1.0 is the bottom side of the image.
71    pub v_stretch: SmallVec<[(f32, f32); 2]>,
72    /// Horizontal content area in the 9 patch image. Content can be placed
73    /// in the region defined here.
74    /// Defined in relative coordinates: 0.0 is the left side of the image,
75    /// 1.0 is the right side of the image.
76    pub h_content: (f32, f32),
77    /// Vertical content area in the 9 patch image. Content can be placed
78    ///  in the region defined here.
79    /// Defined in relative coordinates: 0.0 is the top side of the image,
80    /// 1.0 is the bottom side of the image.
81    pub v_content: (f32, f32),
82}
83
84/// Generic background definition
85#[derive(Clone, Debug)]
86pub enum Background {
87    /// Draw no background
88    None,
89    /// Draw a solid color
90    Color(Color),
91    /// Draw a stretched image multiplied by a color
92    Image(ImageData, Color),
93    /// Draw a 9 patch image multiplied by a color
94    Patch(Patch, Color),
95}
96
97/// A collection of data needed to render the ui.
98pub struct DrawList {
99    /// A list of texture updates that need to be uploaded before rendering.
100    pub updates: Vec<Update>,
101    /// The vertex buffer used for this frame.
102    pub vertices: Vec<Vertex>,
103    /// A list of draw commands that use the `vertices` buffer.
104    pub commands: Vec<Command>,
105}
106
107/// An update of the available texture data. The backend is responsible for uploading the provided
108/// data to the GPU.
109pub enum Update {
110    /// An existing texture is updated.
111    TextureSubresource {
112        /// The id of the texture that needs to be updated
113        id: usize,
114        /// Offset from the left top corner of the texture.
115        offset: [u32; 2],
116        /// Size of the rect described by `data`
117        size: [u32; 2],
118        /// The texel data of the updated rect. 4 elements per pixel.
119        data: Vec<u8>,
120    },
121    /// A new texture is introduced.
122    Texture {
123        /// The id for the new texture. This is the id that will later be used to identify which
124        /// texture the backend has to use whenever applicable.
125        id: usize,
126        /// Size of the texture
127        size: [u32; 2],
128        /// The texel data of the texture. 4 elements per pixel
129        data: Vec<u8>,
130        /// Whether the texture will be used as atlas. `true` means the texture might be updated
131        /// later with [`TextureSubresource`](#variant.TextureSubresource), while `false` means the texture is
132        /// immutable.
133        atlas: bool,
134    },
135}
136
137/// The `Vertex` type passed to the vertex shader.
138#[derive(Debug, Clone, Copy, AsBytes)]
139#[repr(packed)]
140pub struct Vertex {
141    /// The position of the vertex within device coordinates.
142    /// [-1.0, -1.0] is the left top position of the display.
143    pub pos: [f32; 2],
144    /// The coordinates of the texture used by this `Vertex`.
145    /// [0.0, 0.0] is the left top position of the texture.
146    pub uv: [f32; 2],
147    /// A color associated with the `Vertex`.
148    /// The color is multiplied by the end result of the fragment shader.
149    /// When `mode` is not 1, the default value is white ([1.0; 4])
150    pub color: [f32; 4],
151    /// The mode with which the `Vertex` will be drawn within the fragment shader.
152    ///
153    /// `0` for rendering an image.
154    /// `1` for rendering non-textured 2D geometry.
155    ///
156    /// If any other value is given, the fragment shader will not output any color.
157    pub mode: f32,
158}
159
160/// A draw `Command` that is to be translated to a draw command specific to the backend
161#[derive(Debug, Clone, Copy)]
162pub enum Command {
163    /// Do nothing. Appending a `Nop` to another command will flush the other command.
164    Nop,
165    /// Sets a new scissor rect, which is used to confine geometry to a certain area on screen.
166    Clip {
167        /// The scissor rectangle
168        scissor: Rectangle,
169    },
170    /// Draw a list of vertices without an active texture
171    Colored {
172        /// Offset in vertices from the start of the [vertex buffer](struct.DrawList.html#field.vertices)
173        offset: usize,
174        /// The number of vertices to draw
175        count: usize,
176    },
177    /// Draw a list of vertices with the active texture denoted by it's index
178    Textured {
179        /// Texture id to be used
180        texture: usize,
181        /// Offset in vertices from the start of the [vertex buffer](struct.DrawList.html#field.vertices)
182        offset: usize,
183        /// The number of vertices to draw
184        count: usize,
185    },
186}
187
188impl Color {
189    /// Returns an (r, g, b) color with an alpha value of 1.
190    pub fn rgb(r: f32, g: f32, b: f32) -> Self {
191        Self::rgba(r, g, b, 1.0)
192    }
193
194    /// Returns an (r, g, b, a) color.
195    pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
196        Self { r, g, b, a }
197    }
198
199    /// Returns the color white
200    pub fn white() -> Color {
201        Color {
202            r: 1.0,
203            g: 1.0,
204            b: 1.0,
205            a: 1.0,
206        }
207    }
208
209    /// Returns the color black
210    pub fn black() -> Color {
211        Color {
212            r: 0.0,
213            g: 0.0,
214            b: 0.0,
215            a: 1.0,
216        }
217    }
218
219    /// Returns the color red
220    pub fn red() -> Color {
221        Color {
222            r: 1.0,
223            g: 0.0,
224            b: 0.0,
225            a: 1.0,
226        }
227    }
228
229    /// Returns the color green
230    pub fn green() -> Color {
231        Color {
232            r: 0.0,
233            g: 1.0,
234            b: 0.0,
235            a: 1.0,
236        }
237    }
238
239    /// Returns the color blue
240    pub fn blue() -> Color {
241        Color {
242            r: 0.0,
243            g: 0.0,
244            b: 1.0,
245            a: 1.0,
246        }
247    }
248
249    /// Modifies a color with a new alpha component
250    pub fn with_alpha(mut self, a: f32) -> Self {
251        self.a = a;
252        self
253    }
254
255    /// Modifies a color by blending it towards another color
256    pub fn blend(mut self, other: Color, factor: f32) -> Self {
257        let inverse = 1.0 - factor;
258        self.r = self.r * inverse + other.r * factor;
259        self.g = self.g * inverse + other.g * factor;
260        self.b = self.b * inverse + other.b * factor;
261        self.a = self.a * inverse + other.a * factor;
262        self
263    }
264}
265
266impl Patch {
267    /// Extend `measured_content` so it exactly fills the content rect of this patch.
268    pub fn measure_with_content(&self, measured_content: Rectangle) -> Rectangle {
269        let patch_content = self.image.size.sub(Rectangle {
270            left: self.h_content.0,
271            right: self.h_content.1,
272            top: self.v_content.0,
273            bottom: self.v_content.1,
274        });
275
276        let grow_x = (measured_content.width() - patch_content.width()).max(0.0);
277        let grow_y = (measured_content.height() - patch_content.height()).max(0.0);
278
279        Rectangle {
280            left: 0.0,
281            top: 0.0,
282            right: self.image.size.width() + grow_x,
283            bottom: self.image.size.height() + grow_y,
284        }
285    }
286
287    /// Returns the padding of the 9 patch
288    pub fn margin(&self) -> Rectangle {
289        let patch_content = self.image.size.sub(Rectangle {
290            left: self.h_content.0,
291            right: self.h_content.1,
292            top: self.v_content.0,
293            bottom: self.v_content.1,
294        });
295
296        Rectangle {
297            left: patch_content.left,
298            right: self.image.size.right - patch_content.right,
299            top: patch_content.top,
300            bottom: self.image.size.bottom - patch_content.bottom,
301        }
302    }
303
304    /// The size of the patch when the content rect is zero sized.
305    pub fn minimum_size(&self) -> (f32, f32) {
306        let margin = self.margin();
307        (
308            self.image.size.width() - margin.left - margin.right,
309            self.image.size.height() - margin.top - margin.bottom,
310        )
311    }
312
313    /// The content rect for a give size
314    pub fn content_rect(&self, span: Rectangle) -> Rectangle {
315        let mut result = span;
316
317        let blend = |(a, b), x| a + (b - a) * x;
318        let unblend = |x, (a, b)| (x - a) / (b - a);
319
320        self.iterate_sections(false, span.width(), |x, u| {
321            if self.h_content.0 >= u.0 && self.h_content.0 < u.1 {
322                result.left = span.left + blend(x, unblend(self.h_content.0, u));
323            }
324            if self.h_content.1 > u.0 && self.h_content.1 <= u.1 {
325                result.right = span.left + blend(x, unblend(self.h_content.1, u));
326            }
327        });
328        self.iterate_sections(true, span.height(), |y, v| {
329            if self.v_content.0 >= v.0 && self.v_content.0 < v.1 {
330                result.top = span.top + blend(y, unblend(self.v_content.0, v));
331            }
332            if self.v_content.1 > v.0 && self.v_content.1 <= v.1 {
333                result.bottom = span.top + blend(y, unblend(self.v_content.1, v));
334            }
335        });
336
337        result
338    }
339
340    pub(crate) fn iterate_sections<F: FnMut((f32, f32), (f32, f32))>(
341        &self,
342        vertical: bool,
343        length: f32,
344        mut callback: F,
345    ) {
346        let stretches = if vertical { &self.v_stretch } else { &self.h_stretch };
347
348        let total = stretches.iter().fold(0.0, |t, &(a, b)| t + (b - a));
349
350        let mut cursor = 0.0;
351        let mut grow = 0.0;
352
353        let base = if vertical {
354            (0.0, self.image.size.height())
355        } else {
356            (0.0, self.image.size.width())
357        };
358
359        let sub = |x| base.0 + (base.1 - base.0) * x;
360
361        let space = length - base.1;
362
363        for s in stretches.iter() {
364            if s.0 > 0.0 {
365                callback((sub(cursor) + grow, sub(s.0) + grow), (cursor, s.0));
366            }
367
368            let stretch = (s.1 - s.0) / total * space;
369
370            callback((sub(s.0) + grow, sub(s.1) + grow + stretch), (s.0, s.1));
371            cursor = s.1;
372            grow += stretch;
373        }
374        if cursor < 1.0 {
375            callback((sub(cursor) + grow, sub(1.0) + grow), (cursor, 1.0));
376        }
377    }
378}
379
380impl Background {
381    /// Content rect for a given size and padding
382    pub fn content_rect(&self, layout: Rectangle, padding: Rectangle) -> Rectangle {
383        match self {
384            Background::Patch(ref patch, _) => patch.content_rect(layout).after_padding(padding),
385            _ => layout.after_padding(padding),
386        }
387    }
388
389    /// Layout rect for a given content size and padding.
390    /// This is the inverse of [`content_rect`](#method.content_rect)
391    pub fn layout_rect(&self, content_rect: Rectangle, padding: Rectangle) -> Rectangle {
392        match self {
393            Background::Patch(ref patch, _) => patch.measure_with_content(content_rect.after_margin(padding)),
394            _ => content_rect.after_margin(padding),
395        }
396    }
397
398    /// Resolve the size of a widget when taking this background and padding into account
399    pub fn resolve_size(&self, widget: (Size, Size), content: (Size, Size), padding: Rectangle) -> (Size, Size) {
400        let (width, height) = match (widget, content) {
401            ((Size::Shrink, Size::Shrink), (Size::Exact(width), Size::Exact(height))) => {
402                let rect = self.layout_rect(Rectangle::from_wh(width, height), padding);
403                (Size::Exact(rect.width()), Size::Exact(rect.height()))
404            }
405            ((Size::Shrink, other), (Size::Exact(width), _)) => {
406                let rect = self.layout_rect(Rectangle::from_wh(width, 0.0), padding);
407                (Size::Exact(rect.width()), other)
408            }
409            ((other, Size::Shrink), (_, Size::Exact(height))) => {
410                let rect = self.layout_rect(Rectangle::from_wh(0.0, height), padding);
411                (other, Size::Exact(rect.height()))
412            }
413            (other, _) => other,
414        };
415        match (width, height) {
416            (Size::Shrink, Size::Shrink) => (Size::Exact(0.0), Size::Exact(0.0)),
417            (Size::Shrink, other) => (Size::Exact(0.0), other),
418            (other, Size::Shrink) => (other, Size::Exact(0.0)),
419            other => other,
420        }
421    }
422
423    /// Size of the background if the content rect is zero sized
424    pub fn minimum_size(&self) -> (f32, f32) {
425        match self {
426            Background::Patch(patch, _) => patch.minimum_size(),
427            Background::Image(image, _) => (image.size.width(), image.size.height()),
428            _ => (0.0, 0.0),
429        }
430    }
431
432    /// Padding of the background. Only defined for 9 patch backgrounds, other backgrounds have no padding.
433    pub fn padding(&self) -> Rectangle {
434        match self {
435            Background::Patch(ref patch, _) => patch.margin(),
436            _ => Rectangle::zero(),
437        }
438    }
439
440    /// Returns whether the background is visible
441    pub fn is_solid(&self) -> bool {
442        !matches!(self, Background::None)
443    }
444
445    /// Convert background to [`Some(Primitive)`](enum.Primitive.html),
446    /// or `None` if this background is [`None`](#variant.None)
447    pub fn render(&self, rectangle: Rectangle) -> Option<Primitive<'static>> {
448        match self {
449            Background::Color(color) => Some(Primitive::DrawRect(rectangle, *color)),
450            Background::Image(image, color) => Some(Primitive::DrawImage(image.clone(), rectangle, *color)),
451            Background::Patch(patch, color) => Some(Primitive::Draw9(patch.clone(), rectangle, *color)),
452            Background::None => None,
453        }
454    }
455}
456
457impl Command {
458    /// Append another `Command` to this `Command`. If the `Command`s can be chained together
459    /// the `Command` is extended and `None` is returned, but if the `Command`s can not be chained
460    /// the new command is returned again.
461    pub fn append(&mut self, command: Command) -> Option<Command> {
462        match *self {
463            Command::Nop => {
464                *self = command;
465                None
466            }
467
468            Command::Clip { .. } => match command {
469                Command::Nop => None,
470                other => Some(other),
471            },
472
473            Command::Colored { offset, count } => match command {
474                Command::Nop => None,
475                Command::Colored {
476                    offset: new_offset,
477                    count: new_count,
478                } => {
479                    if new_offset == offset + count {
480                        *self = Command::Colored {
481                            offset,
482                            count: count + new_count,
483                        };
484                        None
485                    } else {
486                        Some(command)
487                    }
488                }
489                other => Some(other),
490            },
491
492            Command::Textured { texture, offset, count } => match command {
493                Command::Nop => None,
494                Command::Textured {
495                    texture: new_texture,
496                    offset: new_offset,
497                    count: new_count,
498                } => {
499                    if texture == new_texture && new_offset == offset + count {
500                        *self = Command::Textured {
501                            texture,
502                            offset,
503                            count: count + new_count,
504                        };
505                        None
506                    } else {
507                        Some(command)
508                    }
509                }
510                other => Some(other),
511            },
512        }
513    }
514}