Skip to main content

truce_gui/
backend_cpu.rs

1//! CPU rendering backend using tiny-skia.
2//!
3//! Renders to an in-memory RGBA pixel buffer (premultiplied alpha, row-major).
4
5use tiny_skia::{Paint, PathBuilder, Pixmap, Stroke, Transform};
6
7use crate::render::RenderBackend;
8use crate::theme::Color;
9
10/// CPU-based rendering backend.
11///
12/// Wraps a tiny-skia `Pixmap` and implements `RenderBackend` using
13/// software rasterization. Zero GPU dependencies.
14pub struct CpuBackend {
15    pixmap: Pixmap,
16}
17
18impl CpuBackend {
19    /// Create a new CPU backend with the given pixel dimensions.
20    pub fn new(width: u32, height: u32) -> Option<Self> {
21        Pixmap::new(width, height).map(|pixmap| Self { pixmap })
22    }
23
24    /// Raw pixel data (RGBA premultiplied, row-major).
25    pub fn data(&self) -> &[u8] {
26        self.pixmap.data()
27    }
28
29    /// Pixel buffer width.
30    pub fn width(&self) -> u32 {
31        self.pixmap.width()
32    }
33
34    /// Pixel buffer height.
35    pub fn height(&self) -> u32 {
36        self.pixmap.height()
37    }
38}
39
40impl RenderBackend for CpuBackend {
41    fn clear(&mut self, color: Color) {
42        self.pixmap.fill(color.to_skia());
43    }
44
45    fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32, color: Color) {
46        let rect = match tiny_skia::Rect::from_xywh(x, y, w, h) {
47            Some(r) => r,
48            None => return,
49        };
50        let mut paint = Paint::default();
51        paint.set_color(color.to_skia());
52        paint.anti_alias = true;
53        self.pixmap
54            .fill_rect(rect, &paint, Transform::identity(), None);
55    }
56
57    fn fill_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color) {
58        let mut pb = PathBuilder::new();
59        pb.push_circle(cx, cy, radius);
60        let path = match pb.finish() {
61            Some(p) => p,
62            None => return,
63        };
64        let mut paint = Paint::default();
65        paint.set_color(color.to_skia());
66        paint.anti_alias = true;
67        self.pixmap.fill_path(
68            &path,
69            &paint,
70            tiny_skia::FillRule::Winding,
71            Transform::identity(),
72            None,
73        );
74    }
75
76    fn stroke_circle(&mut self, cx: f32, cy: f32, radius: f32, color: Color, width: f32) {
77        let mut pb = PathBuilder::new();
78        pb.push_circle(cx, cy, radius);
79        let path = match pb.finish() {
80            Some(p) => p,
81            None => return,
82        };
83        let mut paint = Paint::default();
84        paint.set_color(color.to_skia());
85        paint.anti_alias = true;
86        let stroke = Stroke {
87            width,
88            ..Stroke::default()
89        };
90        self.pixmap
91            .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
92    }
93
94    fn stroke_arc(
95        &mut self,
96        cx: f32,
97        cy: f32,
98        radius: f32,
99        start_angle: f32,
100        end_angle: f32,
101        color: Color,
102        width: f32,
103    ) {
104        let segments = 64;
105        let mut pb = PathBuilder::new();
106        let angle_range = end_angle - start_angle;
107        let step = angle_range / segments as f32;
108
109        for i in 0..=segments {
110            let angle = start_angle + step * i as f32;
111            let x = cx + radius * angle.cos();
112            let y = cy + radius * angle.sin();
113            if i == 0 {
114                pb.move_to(x, y);
115            } else {
116                pb.line_to(x, y);
117            }
118        }
119
120        let path = match pb.finish() {
121            Some(p) => p,
122            None => return,
123        };
124        let mut paint = Paint::default();
125        paint.set_color(color.to_skia());
126        paint.anti_alias = true;
127        let stroke = Stroke {
128            width,
129            line_cap: tiny_skia::LineCap::Round,
130            ..Stroke::default()
131        };
132        self.pixmap
133            .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
134    }
135
136    fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: Color, width: f32) {
137        let mut pb = PathBuilder::new();
138        pb.move_to(x1, y1);
139        pb.line_to(x2, y2);
140        let path = match pb.finish() {
141            Some(p) => p,
142            None => return,
143        };
144        let mut paint = Paint::default();
145        paint.set_color(color.to_skia());
146        paint.anti_alias = true;
147        let stroke = Stroke {
148            width,
149            line_cap: tiny_skia::LineCap::Round,
150            ..Stroke::default()
151        };
152        self.pixmap
153            .stroke_path(&path, &paint, &stroke, Transform::identity(), None);
154    }
155
156    fn draw_text(&mut self, text: &str, x: f32, y: f32, size: f32, color: Color) {
157        let w = self.pixmap.width();
158        let h = self.pixmap.height();
159        crate::font::draw_text_fontdue(
160            self.pixmap.data_mut(),
161            w, h,
162            text, x, y, size,
163            color.r, color.g, color.b, color.a,
164        );
165    }
166
167    fn text_width(&self, text: &str, size: f32) -> f32 {
168        crate::font::text_width_fontdue(text, size)
169    }
170}