gpui_qrcode/
lib.rs

1//! Render a QR code as qpui elements
2//!
3//! A simple Component to render a QR code as qpui elements with gpui-native APIs for
4//! customization and flexibility.
5//!
6//! To render a QR code just pass the size and a vector of booleans representing the data, where
7//! true means this block needs to be rendered as a dark square. The size indicates how many columns
8//! to sort the qrcode into, thus the data should usually have the a length of size squared.
9//!
10//! Examples:
11//! ```
12//! use gpui_qrcode::QrCode;
13//! use gpui::prelude::*;
14//!
15//! let qr_code = QrCode::new(10, vec![true, false, true, false]);
16//! ```
17//!
18//! You can also customize the appearance of the QR code by applying styles:
19//! ```
20//! use gpui_qrcode::QrCode;
21//! use gpui::prelude::*;
22//!
23//! let qr_code = QrCode::new(10, vec![true, false, true, false])
24//!     .bg(gpui::blue())
25//!     .p_2();
26//! ```
27//!
28//! To customize the appearance of the QR code dots use the `refine_dot_style` method:
29//! ```
30//! use gpui_qrcode::QrCode;
31//! use gpui::{prelude::*, StyleRefinement};
32//!
33//! let qr_code = QrCode::new(10, vec![true, false, true, false])
34//!     .bg(gpui::blue())
35//!     .p_2()
36//!     .refine_dot_style(&StyleRefinement {
37//!     background: Some(gpui::red().into()),
38//!     ..Default::default()
39//! });
40//! ```
41//!
42
43use gpui::{
44    AbsoluteLength, DefiniteLength, IntoElement, Length, ParentElement, Refineable, RenderOnce,
45    SizeRefinement, StyleRefinement, Styled, div,
46};
47
48#[cfg(feature = "qrcode-support")]
49mod qrcode_lib_support {
50    /// Support for rendering QR codes directly from `qrcode::QrCode`.
51
52    #[derive(Debug, thiserror::Error)]
53    pub enum QrCodeRenderError {
54        #[error("Exceeded width of u16::MAX")]
55        ExceededWidth,
56    }
57
58    impl TryFrom<qrcode::QrCode> for super::QrCode {
59        type Error = QrCodeRenderError;
60
61        fn try_from(data: qrcode::QrCode) -> Result<Self, Self::Error> {
62            let width =
63                u16::try_from(data.width()).map_err(|_| QrCodeRenderError::ExceededWidth)?;
64            Ok(Self::new(
65                width,
66                data.into_colors()
67                    .into_iter()
68                    .map(|color| matches!(color, qrcode::Color::Dark))
69                    .collect(),
70            ))
71        }
72    }
73}
74
75/// A simple Component to render a QR code as qpui elements
76///
77/// This is a `Styled` component, so you can use the regular styling methods of
78/// gpui like `.bg(gpui::blue())` to set the background or `.p_2()` to add padding.
79///
80/// To change the color and behavior of the dots apply the styles you want via the
81/// `refine_dot_style` method.
82#[derive(Clone, Debug, PartialEq, IntoElement)]
83pub struct QrCode {
84    cols: u16,
85    data: Vec<bool>,
86    style: StyleRefinement,
87    dot_style: StyleRefinement,
88}
89
90impl QrCode {
91    /// Create a new QR code component with of `cols` dots per row.
92    ///
93    /// For each entry in `data`, a dot will be rendered if the entry is `true`, thus
94    /// the data is expected to be of length `cols * cols` (but that isn't enforced).
95    pub fn new(cols: u16, data: Vec<bool>) -> Self {
96        Self {
97            cols,
98            data,
99
100            style: StyleRefinement {
101                background: Some(gpui::white().into()),
102                ..Default::default()
103            },
104            dot_style: StyleRefinement {
105                background: Some(gpui::black().into()),
106                min_size: SizeRefinement {
107                    width: Some(Length::Definite(DefiniteLength::Absolute(
108                        AbsoluteLength::Rems(gpui::Rems(0.25)),
109                    ))),
110                    height: Some(Length::Definite(DefiniteLength::Absolute(
111                        AbsoluteLength::Rems(gpui::Rems(0.25)),
112                    ))),
113                },
114                ..Default::default()
115            },
116        }
117    }
118
119    /// Refine the style of the dots in the QR code.
120    ///
121    /// For example, you can set the background color to red like this:
122    ///
123    /// ```rust
124    /// # use gpui_qrcode::QrCode;
125    /// # use gpui::StyleRefinement;
126    /// let qr_code = QrCode::new(10, vec![true; 100]);
127    /// let qr_code = qr_code.refine_dot_style(&StyleRefinement {
128    ///     background: Some(gpui::red().into()),
129    ///     ..Default::default()
130    /// });
131    /// ```
132    pub fn refine_dot_style(mut self, new_styles: &StyleRefinement) -> Self {
133        self.dot_style.refine(new_styles);
134        self
135    }
136}
137
138impl Styled for QrCode {
139    fn style(&mut self) -> &mut StyleRefinement {
140        &mut self.style
141    }
142}
143
144impl RenderOnce for QrCode {
145    fn render(self, _window: &mut gpui::Window, _cx: &mut gpui::App) -> impl gpui::IntoElement {
146        let mut d = div();
147        d.style().refine(&self.style);
148
149        d.grid()
150            .grid_cols(self.cols)
151            .children(self.data.into_iter().map(|is_dark| {
152                let mut d = div();
153                if is_dark {
154                    d.style().refine(&self.dot_style)
155                }
156                d
157            }))
158    }
159}