Skip to main content

raui_app/components/
canvas.rs

1use crate::render_worker::{
2    RenderWorkerDescriptor, RenderWorkerTaskContext, RenderWorkersViewModel,
3};
4use raui_core::{
5    MessageData, Prefab, PropsData, make_widget,
6    messenger::MessageData,
7    pre_hooks,
8    props::PropsData,
9    widget::{
10        component::{
11            ResizeListenerSignal,
12            image_box::{ImageBoxProps, image_box},
13            use_resize_listener,
14        },
15        context::WidgetContext,
16        node::WidgetNode,
17        unit::area::AreaBoxNode,
18        utils::Color,
19    },
20};
21use serde::{Deserialize, Serialize};
22use spitfire_glow::renderer::GlowTextureFormat;
23use std::sync::Arc;
24
25#[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
26pub struct CanvasProps {
27    #[serde(default)]
28    pub color: Color,
29    #[serde(default)]
30    pub clear: bool,
31}
32
33#[derive(MessageData, Debug, Clone)]
34pub struct RequestCanvasRedrawMessage;
35
36#[derive(MessageData, Clone)]
37pub enum DrawOnCanvasMessage {
38    Function(fn(RenderWorkerTaskContext)),
39    #[allow(clippy::type_complexity)]
40    Generator(Arc<dyn Fn() -> Box<dyn FnOnce(RenderWorkerTaskContext)> + Send + Sync>),
41}
42
43impl DrawOnCanvasMessage {
44    pub fn function(callback: fn(RenderWorkerTaskContext)) -> Self {
45        Self::Function(callback)
46    }
47
48    pub fn generator(
49        callback: impl Fn() -> Box<dyn FnOnce(RenderWorkerTaskContext)> + Send + Sync + 'static,
50    ) -> Self {
51        Self::Generator(Arc::new(callback))
52    }
53}
54
55impl std::fmt::Debug for DrawOnCanvasMessage {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.debug_struct("DrawOnCanvasMessage")
58            .finish_non_exhaustive()
59    }
60}
61
62pub fn use_canvas(ctx: &mut WidgetContext) {
63    ctx.life_cycle.mount(|mut ctx| {
64        let props = ctx.props.read_cloned_or_default::<CanvasProps>();
65        let mut workers = ctx
66            .view_models
67            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)
68            .unwrap()
69            .write::<RenderWorkersViewModel>()
70            .unwrap();
71        workers.add_worker(RenderWorkerDescriptor {
72            id: ctx.id.to_string(),
73            width: 1,
74            height: 1,
75            format: GlowTextureFormat::Rgba,
76            color: [props.color.r, props.color.g, props.color.b, props.color.a],
77        });
78        ctx.messenger
79            .write(ctx.id.to_owned(), RequestCanvasRedrawMessage);
80    });
81
82    ctx.life_cycle.unmount(|mut ctx| {
83        let mut workers = ctx
84            .view_models
85            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)
86            .unwrap()
87            .write::<RenderWorkersViewModel>()
88            .unwrap();
89        workers.remove_worker(ctx.id.as_ref());
90    });
91
92    ctx.life_cycle.change(|mut ctx| {
93        let props = ctx.props.read_cloned_or_default::<CanvasProps>();
94        let mut workers = ctx
95            .view_models
96            .view_model_mut(RenderWorkersViewModel::VIEW_MODEL)
97            .unwrap()
98            .write::<RenderWorkersViewModel>()
99            .unwrap();
100        for msg in ctx.messenger.messages {
101            if let Some(ResizeListenerSignal::Change(size)) = msg.as_any().downcast_ref() {
102                workers.add_worker(RenderWorkerDescriptor {
103                    id: ctx.id.to_string(),
104                    width: size.x as u32,
105                    height: size.y as u32,
106                    format: GlowTextureFormat::Rgba,
107                    color: [props.color.r, props.color.g, props.color.b, props.color.a],
108                });
109                ctx.messenger
110                    .write(ctx.id.to_owned(), RequestCanvasRedrawMessage);
111            } else if let Some(msg) = msg.as_any().downcast_ref::<DrawOnCanvasMessage>() {
112                match msg {
113                    DrawOnCanvasMessage::Function(task) => {
114                        workers.schedule_task(ctx.id.as_ref(), props.clear, *task);
115                    }
116                    DrawOnCanvasMessage::Generator(task) => {
117                        workers.schedule_task(ctx.id.as_ref(), props.clear, (*task)());
118                    }
119                }
120            }
121        }
122    });
123}
124
125#[pre_hooks(use_resize_listener, use_canvas)]
126pub fn canvas(mut context: WidgetContext) -> WidgetNode {
127    let WidgetContext { id, idref, key, .. } = context;
128
129    let content = make_widget!(image_box)
130        .key(key)
131        .maybe_idref(idref.cloned())
132        .with_props(ImageBoxProps::image(id))
133        .into();
134
135    AreaBoxNode {
136        id: id.to_owned(),
137        slot: Box::new(content),
138    }
139    .into()
140}