1use tiny_skia::{Paint, PathBuilder, Pixmap, PixmapPaint, Stroke, Transform};
11use truce_core::cast::len_u32;
12
13use crate::ColorExt;
14use truce_gui_types::render::{ImageId, RenderBackend};
15use truce_gui_types::theme::Color;
16
17pub struct CpuBackend {
22 pixmap: Pixmap,
23 scale: f32,
27 images: Vec<Option<Pixmap>>,
29}
30
31impl CpuBackend {
32 #[must_use]
38 pub fn new(logical_w: u32, logical_h: u32, scale: f32) -> Option<Self> {
39 let scale = scale.max(0.0);
40 let phys_w = crate::platform::to_physical_px(logical_w, f64::from(scale));
41 let phys_h = crate::platform::to_physical_px(logical_h, f64::from(scale));
42 Pixmap::new(phys_w, phys_h).map(|pixmap| Self {
43 pixmap,
44 scale,
45 images: Vec::new(),
46 })
47 }
48
49 pub fn resize(&mut self, logical_w: u32, logical_h: u32, scale: f32) -> bool {
54 let scale = scale.max(0.0);
55 let phys_w = crate::platform::to_physical_px(logical_w, f64::from(scale));
56 let phys_h = crate::platform::to_physical_px(logical_h, f64::from(scale));
57 if phys_w == self.pixmap.width() && phys_h == self.pixmap.height() {
58 self.scale = scale;
59 return false;
60 }
61 match Pixmap::new(phys_w, phys_h) {
62 Some(pm) => {
63 self.pixmap = pm;
64 self.scale = scale;
65 true
66 }
67 None => false,
68 }
69 }
70
71 #[must_use]
73 pub fn data(&self) -> &[u8] {
74 self.pixmap.data()
75 }
76
77 #[must_use]
79 pub fn width(&self) -> u32 {
80 self.pixmap.width()
81 }
82
83 #[must_use]
85 pub fn height(&self) -> u32 {
86 self.pixmap.height()
87 }
88
89 #[must_use]
91 pub fn scale(&self) -> f32 {
92 self.scale
93 }
94}
95
96#[allow(clippy::many_single_char_names)]
103impl RenderBackend for CpuBackend {
104 fn clear(&mut self, color: Color) {
105 self.pixmap.fill(color.to_skia());
106 }
107
108 fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
109 let s = self.scale;
110 let Some(rect) = tiny_skia::Rect::from_xywh(x * s, y * s, w * s, h * s) else {
111 return;
112 };
113 let mut paint = Paint::default();
114 paint.set_color(color.to_skia());
115 paint.anti_alias = true;
116 self.pixmap
117 .fill_rect(rect, &paint, Transform::identity(), None);
118 }
119
120 fn fill_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
121 let s = self.scale;
122 let mut pb = PathBuilder::new();
123 pb.push_circle(cx * s, cy * s, radius * s);
124 let Some(path) = pb.finish() else {
125 return;
126 };
127 let mut paint = Paint::default();
128 paint.set_color(color.to_skia());
129 paint.anti_alias = true;
130 self.pixmap.fill_path(
131 &path,
132 &paint,
133 tiny_skia::FillRule::Winding,
134 Transform::identity(),
135 None,
136 );
137 }
138
139 fn stroke_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color, width: f32) {
140 let s = self.scale;
141 let mut pb = PathBuilder::new();
142 pb.push_circle(cx * s, cy * s, radius * s);
143 let Some(path) = pb.finish() else {
144 return;
145 };
146 let mut paint = Paint::default();
147 paint.set_color(color.to_skia());
148 paint.anti_alias = true;
149 let stroke = Stroke {
150 width: width * s,
151 ..Stroke::default()
152 };
153 self.pixmap
154 .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
155 }
156
157 #[allow(clippy::cast_precision_loss)]
158 fn stroke_arc(
159 &mut self,
160 cx: f32,
161 cy: f32,
162 radius: f32,
163 start_angle: f32,
164 end_angle: f32,
165 color: Color,
166 width: f32,
167 ) {
168 let s = self.scale;
169 let segments = 64;
170 let mut pb = PathBuilder::new();
171 let angle_range = end_angle - start_angle;
172 let step = angle_range / segments as f32;
173
174 for i in 0..=segments {
175 let angle = start_angle + step * i as f32;
176 let x = cx * s + radius * s * angle.cos();
177 let y = cy * s + radius * s * angle.sin();
178 if i == 0 {
179 pb.move_to(x, y);
180 } else {
181 pb.line_to(x, y);
182 }
183 }
184
185 let Some(path) = pb.finish() else {
186 return;
187 };
188 let mut paint = Paint::default();
189 paint.set_color(color.to_skia());
190 paint.anti_alias = true;
191 let stroke = Stroke {
192 width: width * s,
193 line_cap: tiny_skia::LineCap::Round,
194 ..Stroke::default()
195 };
196 self.pixmap
197 .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
198 }
199
200 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: Color, width: f32) {
201 let s = self.scale;
202 let mut pb = PathBuilder::new();
203 pb.move_to(x1 * s, y1 * s);
204 pb.line_to(x2 * s, y2 * s);
205 let Some(path) = pb.finish() else {
206 return;
207 };
208 let mut paint = Paint::default();
209 paint.set_color(color.to_skia());
210 paint.anti_alias = true;
211 let stroke = Stroke {
212 width: width * s,
213 line_cap: tiny_skia::LineCap::Round,
214 ..Stroke::default()
215 };
216 self.pixmap
217 .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
218 }
219
220 fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: Color) {
221 let s = self.scale;
222 let w = self.pixmap.width();
223 let h = self.pixmap.height();
224 crate::font::draw_text_fontdue(
225 self.pixmap.data_mut(),
226 w,
227 h,
228 text,
229 x * s,
230 y * s,
231 size * s,
232 color.r,
233 color.g,
234 color.b,
235 color.a,
236 );
237 }
238
239 fn text_width(&self, text: &str, size: f32) -> f32 {
240 let s = self.scale;
241 crate::font::text_width_fontdue(text, size * s) / s
242 }
243
244 fn register_image(&mut self, rgba: &[u8], width: u32, height: u32) -> ImageId {
245 let Some(mut pm) = Pixmap::new(width, height) else {
246 return ImageId::INVALID;
247 };
248 let expected = (width as usize) * (height as usize) * 4;
249 if rgba.len() < expected {
250 return ImageId::INVALID;
251 }
252 pm.data_mut()[..expected].copy_from_slice(&rgba[..expected]);
253
254 if let Some(slot) = self
255 .images
256 .iter_mut()
257 .enumerate()
258 .find(|(_, s)| s.is_none())
259 {
260 *slot.1 = Some(pm);
261 return ImageId(len_u32(slot.0));
262 }
263 let id = len_u32(self.images.len());
264 self.images.push(Some(pm));
265 ImageId(id)
266 }
267
268 fn unregister_image(&mut self, id: ImageId) {
269 if let Some(slot) = self.images.get_mut(id.0 as usize) {
270 *slot = None;
271 }
272 }
273
274 #[allow(clippy::cast_precision_loss)]
277 fn draw_image(&mut self, id: ImageId, x: f32, y: f32, w: f32, h: f32) {
278 let s = self.scale;
279 let Some(pm) = self.images.get(id.0 as usize).and_then(|s| s.as_ref()) else {
280 return;
281 };
282 let sx = (w * s) / pm.width() as f32;
283 let sy = (h * s) / pm.height() as f32;
284 let transform = Transform::from_scale(sx, sy).post_translate(x * s, y * s);
285 let paint = PixmapPaint::default();
286 self.pixmap
287 .draw_pixmap(0, 0, pm.as_ref(), &paint, transform, None);
288 }
289}