rosace_widgets/tree/
shader_paint.rs1use rosace_shader::ShaderMaterial;
31use super::{Widget, LayoutCtx, PaintCtx};
32
33pub struct ShaderPaint {
34 material: ShaderMaterial,
35 width: Option<f32>,
36 height: Option<f32>,
37 animated: bool,
43}
44
45impl ShaderPaint {
46 pub fn new(material: ShaderMaterial) -> Self {
47 Self { material, width: None, height: None, animated: false }
48 }
49
50 pub fn width(mut self, w: f32) -> Self { self.width = Some(w); self }
51 pub fn height(mut self, h: f32) -> Self { self.height = Some(h); self }
52 pub fn size(mut self, w: f32, h: f32) -> Self {
53 self.width = Some(w);
54 self.height = Some(h);
55 self
56 }
57
58 pub fn animated(mut self) -> Self { self.animated = true; self }
69}
70
71impl Widget for ShaderPaint {
72 fn layout(&self, ctx: &LayoutCtx) -> rosace_core::types::Size {
73 let c = ctx.constraints;
74 let w = self.width.unwrap_or_else(|| c.max_width_f32());
75 let h = self.height.unwrap_or_else(|| c.max_height_f32());
76 c.constrain(rosace_core::types::Size {
77 width: if w.is_finite() { w } else { 0.0 },
78 height: if h.is_finite() { h } else { 0.0 },
79 })
80 }
81
82 fn paint(&self, ctx: &mut PaintCtx) {
83 let rect = ctx.rect;
84
85 if let Some(fallback) = self.material.fallback {
91 ctx.fill_rect(rect, fallback);
92 }
93
94 if self.animated {
95 ctx.shader_fill_animated(rect, self.material.pipeline, self.material.uniforms.clone());
96 } else {
97 ctx.shader_fill(rect, self.material.pipeline, self.material.uniforms.clone());
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use rosace_layout::Constraints;
106 use rosace_render::Color;
107 use rosace_shader::PipelineId;
108
109 fn test_material() -> ShaderMaterial {
110 ShaderMaterial::new(PipelineId::user(0x1000), vec![0u8; 16]).fallback(Color::rgb(20, 20, 40))
111 }
112
113 #[test]
114 fn explicit_size_is_honored() {
115 let font = rosace_render::FontCache::embedded();
116 let theme = rosace_theme::built_in::dark_theme();
117 let ctx = LayoutCtx::new(Constraints::loose(400.0, 400.0), &font, &theme);
118 let size = ShaderPaint::new(test_material()).size(200.0, 120.0).layout(&ctx);
119 assert_eq!((size.width, size.height), (200.0, 120.0));
120 }
121
122 #[test]
123 fn unsized_fills_available_bounded_space() {
124 let font = rosace_render::FontCache::embedded();
125 let theme = rosace_theme::built_in::dark_theme();
126 let ctx = LayoutCtx::new(Constraints::loose(300.0, 250.0), &font, &theme);
127 let size = ShaderPaint::new(test_material()).layout(&ctx);
128 assert_eq!((size.width, size.height), (300.0, 250.0));
129 }
130
131 #[test]
132 fn paint_records_a_shader_fill_command() {
133 let font = rosace_render::FontCache::embedded();
134 let theme = rosace_theme::built_in::dark_theme();
135 let mut recorder = rosace_render::PictureRecorder::new();
136 let tree = std::rc::Rc::new(std::cell::RefCell::new(super::super::render_tree::RenderTree::new()));
137 let rect = rosace_core::types::Rect {
138 origin: rosace_core::types::Point { x: 0.0, y: 0.0 },
139 size: rosace_core::types::Size { width: 100.0, height: 100.0 },
140 };
141 let mut ctx = PaintCtx::root(&mut recorder, rect, &font, theme, tree);
142 ShaderPaint::new(test_material()).paint(&mut ctx);
143 let picture = recorder.finish();
144 let has_shader = picture.commands.iter().any(|c| matches!(c, rosace_render::DrawCommand::ShaderFill { .. }));
146 assert!(has_shader, "ShaderPaint must record a ShaderFill draw command");
147 }
148}