msfs/
nvg.rs

1//! NanoVG is small antialiased vector graphics rendering library with a lean
2//! API modeled after the HTML5 Canvas API. It can be used to draw gauge
3//! instruments in MSFS. See `Gauge::create_nanovg`.
4
5use crate::sys;
6
7type Result = std::result::Result<(), Box<dyn std::error::Error>>;
8
9/// A NanoVG render context.
10pub struct Context {
11    ctx: *mut sys::NVGcontext,
12}
13
14impl Context {
15    /// Create a NanoVG render context from an `FsContext`.
16    pub fn create(fs_ctx: sys::FsContext) -> Option<Self> {
17        let uninit = std::mem::MaybeUninit::<sys::NVGparams>::zeroed();
18        let mut params = unsafe { uninit.assume_init() };
19        params.userPtr = fs_ctx;
20        params.edgeAntiAlias = 1;
21
22        let ctx = unsafe { sys::nvgCreateInternal(&mut params) };
23        if ctx.is_null() {
24            None
25        } else {
26            Some(Self { ctx })
27        }
28    }
29
30    /// Draw a frame.
31    pub fn draw_frame<F: Fn(&Frame) -> Result>(&self, width: usize, height: usize, f: F) {
32        unsafe {
33            sys::nvgBeginFrame(self.ctx, width as f32, height as f32, 1.0);
34        }
35
36        let frame = Frame { ctx: self.ctx };
37
38        match f(&frame) {
39            Ok(()) => unsafe {
40                sys::nvgEndFrame(self.ctx);
41            },
42            Err(_) => unsafe {
43                sys::nvgCancelFrame(self.ctx);
44            },
45        }
46    }
47
48    /// NanoVG allows you to load .ttf files and use the font to render text.
49    ///
50    /// The appearance of the text can be defined by setting the current text style
51    /// and by specifying the fill color. Common text and font settings such as
52    /// font size, letter spacing and text align are supported. Font blur allows you
53    /// to create simple text effects such as drop shadows.
54    ///
55    /// At render time the font face can be set based on the font handles or name.
56    ///
57    /// Font measure functions return values in local space, the calculations are
58    /// carried in the same resolution as the final rendering. This is done because
59    /// the text glyph positions are snapped to the nearest pixels sharp rendering.
60    ///
61    /// The local space means that values are not rotated or scale as per the current
62    /// transformation. For example if you set font size to 12, which would mean that
63    /// line height is 16, then regardless of the current scaling and rotation, the
64    /// returned line height is always 16. Some measures may vary because of the scaling
65    /// since aforementioned pixel snapping.
66    ///
67    /// While this may sound a little odd, the setup allows you to always render the
68    /// same way regardless of scaling.
69    ///
70    /// Note: currently only solid color fill is supported for text.
71    pub fn create_font(
72        &self,
73        name: &str,
74        filename: &str,
75    ) -> std::result::Result<Font, Box<dyn std::error::Error>> {
76        let name = std::ffi::CString::new(name).unwrap();
77        let filename = std::ffi::CString::new(filename).unwrap();
78        let handle = unsafe { sys::nvgCreateFont(self.ctx, name.as_ptr(), filename.as_ptr()) };
79        match handle {
80            -1 => Err(Box::new(std::io::Error::new(
81                std::io::ErrorKind::Other,
82                "unable to load font",
83            ))),
84            _ => Ok(Font { handle }),
85        }
86    }
87
88    /// NanoVG allows you to load jpg, png, psd, tga, pic and gif files to be used for rendering.
89    /// In addition you can upload your own image. The image loading is provided by stb_image.
90    pub fn create_image(
91        &self,
92        filename: &str,
93    ) -> std::result::Result<Image, Box<dyn std::error::Error>> {
94        let filename = std::ffi::CString::new(filename).unwrap();
95        let handle = unsafe { sys::nvgCreateImage(self.ctx, filename.as_ptr(), 0) };
96        match handle {
97            -1 => Err(Box::new(std::io::Error::new(
98                std::io::ErrorKind::Other,
99                "unable to load image",
100            ))),
101            _ => Ok(Image {
102                ctx: self.ctx,
103                handle,
104            }),
105        }
106    }
107}
108
109impl Drop for Context {
110    fn drop(&mut self) {
111        unsafe {
112            sys::nvgDeleteInternal(self.ctx);
113        }
114    }
115}
116
117/// Methods to draw on a frame. See `Context::draw_frame`.
118pub struct Frame {
119    ctx: *mut sys::NVGcontext,
120}
121
122impl Frame {
123    /// Draw a path.
124    pub fn draw_path<F: Fn(&Path) -> Result>(&self, style: &Style, f: F) -> Result {
125        unsafe {
126            // sys::nvgSave(self.ctx);
127            // sys::nvgReset(self.ctx);
128            sys::nvgBeginPath(self.ctx);
129        }
130
131        if let Some(stroke) = &style.stroke {
132            match stroke {
133                PaintOrColor::Paint(p) => unsafe {
134                    sys::nvgStrokePaint(self.ctx, &p.0);
135                },
136                PaintOrColor::Color(c) => unsafe {
137                    sys::nvgStrokeColor(self.ctx, &c.0);
138                },
139            }
140        }
141        if let Some(fill) = &style.fill {
142            match fill {
143                PaintOrColor::Paint(p) => unsafe {
144                    sys::nvgFillPaint(self.ctx, &p.0);
145                },
146                PaintOrColor::Color(c) => unsafe {
147                    sys::nvgFillColor(self.ctx, &c.0);
148                },
149            }
150        }
151
152        let path = Path { ctx: self.ctx };
153        let r = f(&path);
154
155        if style.stroke.is_some() {
156            unsafe {
157                sys::nvgStroke(self.ctx);
158            }
159        }
160        if style.fill.is_some() {
161            unsafe {
162                sys::nvgFill(self.ctx);
163            }
164        }
165
166        /*
167        unsafe {
168            sys::nvgRestore(self.ctx);
169        }
170        */
171
172        r
173    }
174}
175
176/// A path.
177pub struct Path {
178    ctx: *mut sys::NVGcontext,
179}
180
181impl Path {
182    /// Starts new sub-path with specified point as first point.
183    pub fn move_to(&self, x: f32, y: f32) {
184        unsafe {
185            sys::nvgMoveTo(self.ctx, x, y);
186        }
187    }
188
189    /// Adds line segment from the last point in the path to the specified point.
190    pub fn line_to(&self, x: f32, y: f32) {
191        unsafe {
192            sys::nvgLineTo(self.ctx, x, y);
193        }
194    }
195
196    /// Adds cubic bezier segment from last point in the path via two control points to the specified point.
197    pub fn bezier_to(&self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) {
198        unsafe {
199            sys::nvgBezierTo(self.ctx, c1x, c1y, c2x, c2y, x, y);
200        }
201    }
202
203    /// Adds quadratic bezier segment from last point in the path via a control point to the
204    /// specified point.
205    pub fn quad_to(&self, cx: f32, cy: f32, x: f32, y: f32) {
206        unsafe {
207            sys::nvgQuadTo(self.ctx, cx, cy, x, y);
208        }
209    }
210
211    /// Adds an arc segment at the corner defined by the last path point, and two specified points.
212    pub fn arc_to(&self, x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) {
213        unsafe {
214            sys::nvgArcTo(self.ctx, x1, y1, x2, y2, radius);
215        }
216    }
217
218    /// Closes current sub-path with a line segment.
219    pub fn close_path(&self) {
220        unsafe {
221            sys::nvgClosePath(self.ctx);
222        }
223    }
224
225    /// Creates a new circle arc shaped sub-path. The arc center is at (`cx`,`cy`), the arc radius
226    /// is `r`, and the arc is drawn from angle `a0` to `a1`, and swept in direction `dir`.
227    /// Angles are in radians.
228    pub fn arc(&self, cx: f32, cy: f32, r: f32, a0: f32, a1: f32, dir: Direction) {
229        unsafe {
230            sys::nvgArc(self.ctx, cx, cy, r, a0, a1, dir.to_sys() as _);
231        }
232    }
233
234    /// Creates a new oval arc shaped sub-path. The arc center is at (`cx`, `cy`), the arc radius
235    /// is (`rx`, `ry`), and the arc is draw from angle a0 to a1, and swept in direction `dir`.
236    #[allow(clippy::too_many_arguments)]
237    pub fn elliptical_arc(
238        &self,
239        cx: f32,
240        cy: f32,
241        rx: f32,
242        ry: f32,
243        a0: f32,
244        a1: f32,
245        dir: Direction,
246    ) {
247        unsafe {
248            sys::nvgEllipticalArc(self.ctx, cx, cy, rx, ry, a0, a1, dir.to_sys() as _);
249        }
250    }
251
252    /// Creates new rectangle shaped sub-path.
253    pub fn rect(&self, x: f32, y: f32, w: f32, h: f32) {
254        unsafe {
255            sys::nvgRect(self.ctx, x, y, w, h);
256        }
257    }
258
259    /// Creates a new rounded rectangle sub-path with rounded corners
260    #[allow(clippy::many_single_char_names)]
261    pub fn rounded_rect(&self, x: f32, y: f32, w: f32, h: f32, r: f32) {
262        unsafe {
263            sys::nvgRoundedRect(self.ctx, x, y, w, h, r);
264        }
265    }
266
267    /// Creates new rounded rectangle shaped sub-path with varying radii for each corner.
268    #[allow(clippy::too_many_arguments)]
269    #[allow(clippy::many_single_char_names)]
270    pub fn rounded_rect_varying(
271        &self,
272        x: f32,
273        y: f32,
274        w: f32,
275        h: f32,
276        rad_top_left: f32,
277        rad_top_right: f32,
278        rad_bottom_right: f32,
279        rad_bottom_left: f32,
280    ) {
281        unsafe {
282            sys::nvgRoundedRectVarying(
283                self.ctx,
284                x,
285                y,
286                w,
287                h,
288                rad_top_left,
289                rad_top_right,
290                rad_bottom_right,
291                rad_bottom_left,
292            );
293        }
294    }
295
296    /// Creates a new ellipse shaped sub-path.
297    pub fn ellipse(&self, cx: f32, cy: f32, rx: f32, ry: f32) {
298        unsafe {
299            sys::nvgEllipse(self.ctx, cx, cy, rx, ry);
300        }
301    }
302
303    /// Creates a new circle shaped path.
304    pub fn circle(&self, cx: f32, cy: f32, r: f32) {
305        unsafe {
306            sys::nvgCircle(self.ctx, cx, cy, r);
307        }
308    }
309
310    // TODO: fill
311}
312
313/// Winding direction
314#[derive(Debug, Clone, Copy)]
315pub enum Direction {
316    /// Winding for holes.
317    Clockwise,
318    /// Winding for solid shapes.
319    CounterClockwise,
320}
321
322impl Direction {
323    fn to_sys(self) -> sys::NVGwinding {
324        match self {
325            Direction::Clockwise => sys::NVGwinding_NVG_CW,
326            Direction::CounterClockwise => sys::NVGwinding_NVG_CCW,
327        }
328    }
329}
330
331#[doc(hidden)]
332pub enum PaintOrColor {
333    Paint(Paint),
334    Color(Color),
335}
336
337impl From<Paint> for PaintOrColor {
338    fn from(p: Paint) -> PaintOrColor {
339        PaintOrColor::Paint(p)
340    }
341}
342
343impl From<Color> for PaintOrColor {
344    fn from(c: Color) -> PaintOrColor {
345        PaintOrColor::Color(c)
346    }
347}
348
349/// The stroke and/or fill which will be applied to a path.
350#[derive(Default)]
351pub struct Style {
352    stroke: Option<PaintOrColor>,
353    fill: Option<PaintOrColor>,
354}
355
356impl Style {
357    /// Set the stroke of this style.
358    pub fn stroke<T: Into<PaintOrColor>>(mut self, stroke: T) -> Self {
359        self.stroke = Some(stroke.into());
360        self
361    }
362
363    /// Set the fill of this style.
364    pub fn fill<T: Into<PaintOrColor>>(mut self, fill: T) -> Self {
365        self.fill = Some(fill.into());
366        self
367    }
368}
369
370/// Colors in NanoVG are stored as unsigned ints in ABGR format.
371pub struct Color(sys::NVGcolor);
372
373impl Color {
374    /// Returns a color value from red, green, blue values. Alpha will be set to 255 (1.0).
375    pub fn from_rgb(r: u8, g: u8, b: u8) -> Self {
376        Self(unsafe { sys::nvgRGB(r, g, b) })
377    }
378
379    /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
380    pub fn from_rgbf(r: f32, g: f32, b: f32) -> Self {
381        Self(unsafe { sys::nvgRGBf(r, g, b) })
382    }
383
384    /// Returns a color value from red, green, blue and alpha values.
385    pub fn from_rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
386        Self(unsafe { sys::nvgRGBA(r, g, b, a) })
387    }
388
389    /// Returns a color value from red, green, blue values. Alpha will be set to 1.0f.
390    pub fn from_rgbaf(r: f32, g: f32, b: f32, a: f32) -> Self {
391        Self(unsafe { sys::nvgRGBAf(r, g, b, a) })
392    }
393
394    /// Returns color value specified by hue, saturation and lightness.
395    /// HSL values are all in range [0..1], alpha will be set to 255.
396    pub fn from_hsv(h: f32, s: f32, l: f32) -> Self {
397        Self(unsafe { sys::nvgHSL(h, s, l) })
398    }
399
400    /// Returns color value specified by hue, saturation and lightness.
401    /// HSL values are all in range [0..1], alpha will be set to 255.
402    pub fn from_hsva(h: f32, s: f32, l: f32, a: u8) -> Self {
403        Self(unsafe { sys::nvgHSLA(h, s, l, a) })
404    }
405}
406
407/// NanoVG supports four types of paints: linear gradient, box gradient, radial gradient and image pattern.
408/// These can be used as paints for strokes and fills.
409pub struct Paint(sys::NVGpaint);
410
411impl Paint {
412    /// Creates and returns an image pattern. Parameters (`x`, `y`) specify the left-top location of the image pattern,
413    /// (`w`, `h`) is the size of the image, `angle` is the rotation around the top-left corner, and `image` is the image
414    /// to render.
415    pub fn from_image(
416        image: &Image,
417        x: f32,
418        y: f32,
419        w: f32,
420        h: f32,
421        angle: f32,
422        alpha: f32,
423    ) -> Paint {
424        Paint(unsafe { sys::nvgImagePattern(image.ctx, x, y, w, h, angle, image.handle, alpha) })
425    }
426}
427
428/// A font handle.
429pub struct Font {
430    handle: std::os::raw::c_int,
431}
432
433/// An image handle.
434pub struct Image {
435    ctx: *mut sys::NVGcontext,
436    handle: std::os::raw::c_int,
437}
438
439impl Image {
440    /// Returns the dimensions of a created image.
441    pub fn size(&self) -> (usize, usize) {
442        let mut w = 0;
443        let mut h = 0;
444        unsafe {
445            sys::nvgImageSize(self.ctx, self.handle, &mut w, &mut h);
446        }
447        (w as usize, h as usize)
448    }
449}
450
451impl Drop for Image {
452    fn drop(&mut self) {
453        unsafe {
454            sys::nvgDeleteImage(self.ctx, self.handle);
455        }
456    }
457}