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}