pixel_widgets/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(missing_docs)]
3
4use std::ops::{Deref, DerefMut};
5use std::sync::Arc;
6use std::task::Waker;
7
8use node::GenericNode;
9use widget::Context;
10
11use crate::component::Component;
12use crate::draw::DrawList;
13use crate::event::Event;
14use crate::layout::Rectangle;
15use crate::node::component_node::ComponentNode;
16use crate::style::tree::Query;
17use crate::style::Style;
18use crate::tracker::ManagedState;
19
20mod atlas;
21/// Backend specific code
22pub mod backend;
23mod bitset;
24/// Texture cache for styles and text
25pub mod cache;
26/// The component trait.
27pub mod component;
28/// Primitives used for drawing
29pub mod draw;
30/// User input events
31pub mod event;
32/// Graphics loader
33pub mod graphics;
34/// Primitives used for layouts
35pub mod layout;
36mod macros;
37/// User interface building blocks
38pub mod node;
39/// Prelude module for pixel-widgets.
40pub mod prelude;
41/// Simple windowing system for those who want to render _just_ widgets.
42#[cfg(feature = "winit")]
43#[cfg(feature = "wgpu")]
44pub mod sandbox;
45/// Styling system
46pub mod style;
47/// Primitives for rendering text
48pub mod text;
49/// Utility for tracking state conveniently.
50pub mod tracker;
51/// User interface widgets
52pub mod widget;
53
54/// Entry point for the user interface.
55///
56/// `Ui` manages a root [`Component`](component/trait.Component.html) and processes it to a
57/// [`DrawList`](draw/struct.DrawList.html) that can be rendered using your own renderer implementation.
58/// Alternatively, you can use one of the following included wrappers:
59/// - [`wgpu::Ui`](backend/wgpu/struct.Ui.html) Renders using [wgpu](https://github.com/gfx-rs/wgpu).
60///
61/// # Async support
62/// Components can submit futures to the [`Context`](component/struct.Context.html) using
63/// [`wait()`](component/struct.Context.html#method.wait) and
64/// [`stream()`](component/struct.Context.html#method.stream). These futures will update
65/// those components when they complete or yield messages.
66/// To support this, you must make sure that the poll method on `Ui` is called appropriately
67/// since the `Ui` can't be submitted to a typical executor.
68///
69/// The [`Sandbox`](sandbox/struct.Sandbox.html) can serve as an example since it
70/// provides such a runtime for you through the winit event loop.
71pub struct Ui<M: 'static + Component> {
72    root_node: ComponentNode<'static, M>,
73    _state: ManagedState,
74    viewport: Rectangle,
75    redraw: bool,
76    cursor: (f32, f32),
77    style: Arc<Style>,
78    hidpi_scale: f32,
79}
80
81impl<C: 'static + Component> Ui<C> {
82    /// Constructs a new `Ui`. Returns an error if the style fails to load.
83    pub fn new<S, E>(root: C, viewport: Rectangle, hidpi_scale: f32, style: S) -> anyhow::Result<Self>
84    where
85        S: TryInto<Style, Error = E>,
86        anyhow::Error: From<E>,
87    {
88        let mut state = ManagedState::default();
89        let mut root_node = ComponentNode::new(root);
90        root_node.acquire_state(&mut unsafe { (&mut state as *mut ManagedState).as_mut() }.unwrap().tracker());
91
92        let style = Arc::new(style.try_into()?);
93        root_node.set_dirty();
94        root_node.style(&mut Query::from_style(style.clone()), (0, 1));
95
96        Ok(Self {
97            root_node,
98            _state: state,
99            viewport: Rectangle {
100                left: viewport.left / hidpi_scale,
101                top: viewport.top / hidpi_scale,
102                right: viewport.right / hidpi_scale,
103                bottom: viewport.bottom / hidpi_scale,
104            },
105            redraw: true,
106            cursor: (0.0, 0.0),
107            style,
108            hidpi_scale,
109        })
110    }
111
112    /// Updates the root component with a message.
113    ///
114    /// If the ui has any pending futures internally, they are polled using the waker.
115    /// Returns output messages from the root component if the update or the poll yielded any.
116    ///
117    /// It's up to the user to make sure that the `waker` will schedule a call to [`poll()`](#method.poll) on this `Ui`.
118    pub fn update_and_poll(&mut self, message: C::Message, waker: Waker) -> Vec<C::Output> {
119        let mut context = Context::new(self.needs_redraw(), self.cursor, waker);
120
121        self.root_node.update(message, &mut context);
122        while self.root_node.needs_poll() {
123            self.root_node.poll(&mut context);
124        }
125
126        self.redraw = context.redraw_requested();
127        context.into_vec()
128    }
129
130    /// Handles a ui [`Event`](event/struct.Event.html).
131    ///
132    /// If the ui has any pending futures internally, they are polled using the waker.
133    /// Returns output messages from the root component if the event or the poll yielded any.
134    ///
135    /// It's up to the user to make sure that the `waker` will schedule a call to [`poll()`](#method.poll) on this `Ui`.
136    pub fn handle_event_and_poll(&mut self, mut event: Event, waker: Waker) -> Vec<C::Output> {
137        if let Event::Cursor(x, y) = event {
138            event = Event::Cursor(x / self.hidpi_scale, y / self.hidpi_scale);
139            self.cursor = (x / self.hidpi_scale, y / self.hidpi_scale);
140        }
141
142        let mut context = Context::new(self.needs_redraw(), self.cursor, waker.clone());
143
144        {
145            let mut view = self.root_node.view();
146            let (w, h) = view.size();
147            let layout = Rectangle::from_wh(
148                w.resolve(self.viewport.width(), w.parts()),
149                h.resolve(self.viewport.height(), h.parts()),
150            );
151            view.event(layout, self.viewport, event, &mut context);
152        }
153
154        self.redraw = context.redraw_requested();
155
156        let mut outer_context = Context::new(self.needs_redraw(), self.cursor, waker);
157
158        for message in context {
159            self.root_node.update(message, &mut outer_context);
160        }
161        while self.root_node.needs_poll() {
162            self.root_node.poll(&mut outer_context);
163        }
164
165        self.redraw = outer_context.redraw_requested();
166        outer_context.into_vec()
167    }
168
169    /// If the ui has any pending futures internally, this method will poll them using the waker.
170    /// Returns output messages from the root component if the poll yielded any.
171    ///
172    /// It's up to the user to make sure that the `waker` will schedule another call to `poll()`.
173    pub fn poll(&mut self, waker: Waker) -> Vec<C::Output> {
174        let mut context = Context::new(self.needs_redraw(), self.cursor, waker);
175        loop {
176            self.root_node.poll(&mut context);
177            self.redraw = context.redraw_requested();
178
179            if self.root_node.needs_poll() {
180                continue;
181            } else {
182                break;
183            }
184        }
185        context.into_vec()
186    }
187
188    /// Resizes the viewport.
189    /// This forces the view to be rerendered.
190    pub fn resize(&mut self, viewport: Rectangle) {
191        self.root_node.set_dirty();
192        self.redraw = true;
193        self.viewport = Rectangle {
194            left: viewport.left / self.hidpi_scale,
195            top: viewport.top / self.hidpi_scale,
196            right: viewport.right / self.hidpi_scale,
197            bottom: viewport.bottom / self.hidpi_scale,
198        };
199    }
200
201    /// Returns true if the ui needs to be redrawn. If the ui doesn't need to be redrawn the
202    /// [`Command`s](draw/struct.Command.html) from the last [`draw`](#method.draw) may be used again.
203    pub fn needs_redraw(&self) -> bool {
204        self.redraw || self.root_node.dirty()
205    }
206
207    /// Generate a [`DrawList`](draw/struct.DrawList.html) for the view.
208    pub fn draw(&mut self) -> DrawList {
209        use self::draw::*;
210
211        let viewport = self.viewport;
212        let primitives = {
213            let mut view = self.root_node.view();
214            let (w, h) = view.size();
215            let layout = Rectangle::from_wh(
216                w.resolve(viewport.width(), w.parts()),
217                h.resolve(viewport.height(), h.parts()),
218            );
219            view.draw(layout, viewport)
220        };
221        self.redraw = false;
222
223        struct Layer {
224            vtx: Vec<Vertex>,
225            cmd: Vec<Command>,
226        }
227
228        impl Layer {
229            fn append(&mut self, command: Command) {
230                if let Some(next) = self.cmd.last_mut().unwrap().append(command) {
231                    self.cmd.push(next);
232                }
233            }
234        }
235
236        let mut layers = vec![Layer {
237            vtx: Vec::new(),
238            cmd: vec![Command::Nop],
239        }];
240        let mut layer: usize = 0;
241
242        let mut scissors = vec![viewport];
243
244        let scale = self.hidpi_scale;
245        let validate_clip = move |clip: Rectangle| {
246            let v = Rectangle {
247                left: clip.left.max(0.0).min(viewport.right) * scale,
248                top: clip.top.max(0.0).min(viewport.bottom) * scale,
249                right: clip.right.max(0.0).min(viewport.right) * scale,
250                bottom: clip.bottom.max(0.0).min(viewport.bottom) * scale,
251            };
252            if v.right as u32 - v.left as u32 > 0 && v.bottom as u32 - v.top as u32 > 0 {
253                Some(v)
254            } else {
255                None
256            }
257        };
258
259        let mut draw_enabled = true;
260
261        for primitive in primitives.into_iter() {
262            match primitive {
263                Primitive::PushClip(scissor) => {
264                    scissors.push(scissor);
265
266                    draw_enabled = validate_clip(scissor).map_or(false, |s| {
267                        layers[layer].append(Command::Clip { scissor: s });
268                        true
269                    });
270                }
271
272                Primitive::PopClip => {
273                    scissors.pop();
274                    let scissor = scissors[scissors.len() - 1];
275
276                    draw_enabled = validate_clip(scissor).map_or(false, |s| {
277                        layers[layer].append(Command::Clip { scissor: s });
278                        true
279                    });
280                }
281
282                Primitive::LayerUp => {
283                    layer += 1;
284                    while layer >= layers.len() {
285                        layers.push(Layer {
286                            vtx: Vec::new(),
287                            cmd: vec![Command::Nop],
288                        });
289                    }
290                }
291
292                Primitive::LayerDown => {
293                    layer -= 1;
294                }
295
296                Primitive::DrawRect(r, color) => {
297                    if draw_enabled {
298                        let r = r.to_device_coordinates(viewport);
299                        let color = [color.r, color.g, color.b, color.a];
300                        let mode = 1.0;
301                        let offset = layers[layer].vtx.len();
302                        layers[layer].vtx.push(Vertex {
303                            pos: [r.left, r.top],
304                            uv: [0.0; 2],
305                            color,
306                            mode,
307                        });
308                        layers[layer].vtx.push(Vertex {
309                            pos: [r.right, r.top],
310                            uv: [0.0; 2],
311                            color,
312                            mode,
313                        });
314                        layers[layer].vtx.push(Vertex {
315                            pos: [r.right, r.bottom],
316                            uv: [0.0; 2],
317                            color,
318                            mode,
319                        });
320                        layers[layer].vtx.push(Vertex {
321                            pos: [r.left, r.top],
322                            uv: [0.0; 2],
323                            color,
324                            mode,
325                        });
326                        layers[layer].vtx.push(Vertex {
327                            pos: [r.right, r.bottom],
328                            uv: [0.0; 2],
329                            color,
330                            mode,
331                        });
332                        layers[layer].vtx.push(Vertex {
333                            pos: [r.left, r.bottom],
334                            uv: [0.0; 2],
335                            color,
336                            mode,
337                        });
338                        layers[layer].append(Command::Colored { offset, count: 6 });
339                    }
340                }
341
342                Primitive::DrawText(text, rect) => {
343                    if draw_enabled {
344                        let color = [text.color.r, text.color.g, text.color.b, text.color.a];
345                        let mode = 0.0;
346                        let offset = layers[layer].vtx.len();
347
348                        self.style.cache().lock().unwrap().draw_text(&text, rect, |uv, pos| {
349                            let rc = Rectangle {
350                                left: pos.left,
351                                top: pos.top,
352                                right: pos.right,
353                                bottom: pos.bottom,
354                            }
355                            .to_device_coordinates(viewport);
356
357                            layers[layer].vtx.push(Vertex {
358                                pos: [rc.left, rc.top],
359                                uv: uv.pt(0.0, 0.0),
360                                color,
361                                mode,
362                            });
363                            layers[layer].vtx.push(Vertex {
364                                pos: [rc.right, rc.top],
365                                uv: uv.pt(1.0, 0.0),
366                                color,
367                                mode,
368                            });
369                            layers[layer].vtx.push(Vertex {
370                                pos: [rc.right, rc.bottom],
371                                uv: uv.pt(1.0, 1.0),
372                                color,
373                                mode,
374                            });
375                            layers[layer].vtx.push(Vertex {
376                                pos: [rc.left, rc.top],
377                                uv: uv.pt(0.0, 0.0),
378                                color,
379                                mode,
380                            });
381                            layers[layer].vtx.push(Vertex {
382                                pos: [rc.right, rc.bottom],
383                                uv: uv.pt(1.0, 1.0),
384                                color,
385                                mode,
386                            });
387                            layers[layer].vtx.push(Vertex {
388                                pos: [rc.left, rc.bottom],
389                                uv: uv.pt(0.0, 1.0),
390                                color,
391                                mode,
392                            });
393                        });
394
395                        let count = layers[layer].vtx.len() - offset;
396                        layers[layer].append(Command::Textured {
397                            texture: text.font.tex_slot,
398                            offset,
399                            count,
400                        });
401                    }
402                }
403
404                Primitive::Draw9(patch, rect, color) => {
405                    if draw_enabled {
406                        let uv = patch.image.texcoords;
407                        let color = [color.r, color.g, color.b, color.a];
408                        let mode = 0.0;
409                        let offset = layers[layer].vtx.len();
410
411                        patch.iterate_sections(false, rect.width(), |x, u| {
412                            patch.iterate_sections(true, rect.height(), |y, v| {
413                                let rc = Rectangle {
414                                    left: x.0 + rect.left,
415                                    right: x.1 + rect.left,
416                                    top: y.0 + rect.top,
417                                    bottom: y.1 + rect.top,
418                                }
419                                .to_device_coordinates(viewport);
420
421                                layers[layer].vtx.push(Vertex {
422                                    pos: [rc.left, rc.top],
423                                    uv: uv.pt(u.0, v.0),
424                                    color,
425                                    mode,
426                                });
427                                layers[layer].vtx.push(Vertex {
428                                    pos: [rc.right, rc.top],
429                                    uv: uv.pt(u.1, v.0),
430                                    color,
431                                    mode,
432                                });
433                                layers[layer].vtx.push(Vertex {
434                                    pos: [rc.right, rc.bottom],
435                                    uv: uv.pt(u.1, v.1),
436                                    color,
437                                    mode,
438                                });
439                                layers[layer].vtx.push(Vertex {
440                                    pos: [rc.left, rc.top],
441                                    uv: uv.pt(u.0, v.0),
442                                    color,
443                                    mode,
444                                });
445                                layers[layer].vtx.push(Vertex {
446                                    pos: [rc.right, rc.bottom],
447                                    uv: uv.pt(u.1, v.1),
448                                    color,
449                                    mode,
450                                });
451                                layers[layer].vtx.push(Vertex {
452                                    pos: [rc.left, rc.bottom],
453                                    uv: uv.pt(u.0, v.1),
454                                    color,
455                                    mode,
456                                });
457                            });
458                        });
459
460                        let count = layers[layer].vtx.len() - offset;
461                        layers[layer].append(Command::Textured {
462                            texture: patch.image.texture,
463                            offset,
464                            count,
465                        });
466                    }
467                }
468
469                Primitive::DrawImage(image, r, color) => {
470                    if draw_enabled {
471                        let r = r.to_device_coordinates(viewport);
472                        let uv = image.texcoords;
473                        let color = [color.r, color.g, color.b, color.a];
474                        let mode = 0.0;
475                        let offset = layers[layer].vtx.len();
476
477                        layers[layer].vtx.push(Vertex {
478                            pos: [r.left, r.top],
479                            uv: [uv.left, uv.top],
480                            color,
481                            mode,
482                        });
483                        layers[layer].vtx.push(Vertex {
484                            pos: [r.right, r.top],
485                            uv: [uv.right, uv.top],
486                            color,
487                            mode,
488                        });
489                        layers[layer].vtx.push(Vertex {
490                            pos: [r.right, r.bottom],
491                            uv: [uv.right, uv.bottom],
492                            color,
493                            mode,
494                        });
495                        layers[layer].vtx.push(Vertex {
496                            pos: [r.left, r.top],
497                            uv: [uv.left, uv.top],
498                            color,
499                            mode,
500                        });
501                        layers[layer].vtx.push(Vertex {
502                            pos: [r.right, r.bottom],
503                            uv: [uv.right, uv.bottom],
504                            color,
505                            mode,
506                        });
507                        layers[layer].vtx.push(Vertex {
508                            pos: [r.left, r.bottom],
509                            uv: [uv.left, uv.bottom],
510                            color,
511                            mode,
512                        });
513
514                        layers[layer].append(Command::Textured {
515                            texture: image.texture,
516                            offset,
517                            count: 6,
518                        });
519                    }
520                }
521            }
522        }
523
524        let (vertices, commands) =
525            layers
526                .into_iter()
527                .fold((Vec::new(), Vec::new()), |(mut vtx, mut cmd), mut layer| {
528                    let layer_offset = vtx.len();
529                    vtx.append(&mut layer.vtx);
530                    cmd.extend(layer.cmd.into_iter().map(|command| match command {
531                        Command::Textured { texture, offset, count } => Command::Textured {
532                            texture,
533                            offset: offset + layer_offset,
534                            count,
535                        },
536                        Command::Colored { offset, count } => Command::Colored {
537                            offset: offset + layer_offset,
538                            count,
539                        },
540                        other => other,
541                    }));
542                    (vtx, cmd)
543                });
544
545        DrawList {
546            updates: self.style.cache().lock().unwrap().take_updates(),
547            vertices,
548            commands,
549        }
550    }
551}
552
553impl<C: Component> Deref for Ui<C> {
554    type Target = C;
555
556    fn deref(&self) -> &Self::Target {
557        self.root_node.props()
558    }
559}
560
561impl<C: Component> DerefMut for Ui<C> {
562    fn deref_mut(&mut self) -> &mut Self::Target {
563        self.root_node.props_mut()
564    }
565}