raui_app/components/
canvas.rs1use 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}