|
| 1 | +#![cfg(feature = "yew")] |
| 2 | + |
| 3 | +use yew::{function_component, html, AttrValue, Callback, Html, MouseEvent, Properties}; |
| 4 | + |
| 5 | +use alloc::format; |
| 6 | +use alloc::string::String; |
| 7 | +use core::fmt::Write; |
| 8 | +use core::ops::Not as _; |
| 9 | + |
| 10 | +use crate::render::{Canvas as RenderCanvas, Pixel}; |
| 11 | +use crate::types::Color as ModuleColor; |
| 12 | +use crate::{EcLevel, Version}; |
| 13 | + |
| 14 | +#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| 15 | +pub struct Color<'a>(pub &'a str); |
| 16 | + |
| 17 | +pub struct SvgAttrs { |
| 18 | + width: u32, |
| 19 | + height: u32, |
| 20 | + fg: String, |
| 21 | + bg: String, |
| 22 | + d: String, |
| 23 | +} |
| 24 | + |
| 25 | +impl<'a> Pixel for Color<'a> { |
| 26 | + type Canvas = Canvas<'a>; |
| 27 | + type Image = SvgAttrs; |
| 28 | + |
| 29 | + fn default_color(color: ModuleColor) -> Self { |
| 30 | + Color(color.select("#000", "#fff")) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +#[doc(hidden)] |
| 35 | +pub struct Canvas<'a> { |
| 36 | + d: String, |
| 37 | + w: u32, |
| 38 | + h: u32, |
| 39 | + fg: &'a str, |
| 40 | + bg: &'a str, |
| 41 | +} |
| 42 | + |
| 43 | +impl<'a> RenderCanvas for Canvas<'a> { |
| 44 | + type Pixel = Color<'a>; |
| 45 | + type Image = SvgAttrs; |
| 46 | + |
| 47 | + fn new(width: u32, height: u32, dark_pixel: Color<'a>, light_pixel: Color<'a>) -> Self { |
| 48 | + Self { d: String::new(), w: width, h: height, fg: dark_pixel.0, bg: light_pixel.0 } |
| 49 | + } |
| 50 | + |
| 51 | + fn draw_dark_pixel(&mut self, x: u32, y: u32) { |
| 52 | + self.draw_dark_rect(x, y, 1, 1); |
| 53 | + } |
| 54 | + |
| 55 | + fn draw_dark_rect(&mut self, left: u32, top: u32, width: u32, height: u32) { |
| 56 | + write!(self.d, "M{left} {top}h{width}v{height}h-{width}z").unwrap(); |
| 57 | + } |
| 58 | + |
| 59 | + fn into_image(self) -> SvgAttrs { |
| 60 | + SvgAttrs { width: self.w, height: self.h, fg: self.fg.to_owned(), bg: self.bg.to_owned(), d: self.d } |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +#[derive(Clone, PartialEq, Properties)] |
| 65 | +pub struct Props { |
| 66 | + pub version: Version, |
| 67 | + pub ec_level: EcLevel, |
| 68 | + |
| 69 | + pub url: AttrValue, |
| 70 | + pub size: AttrValue, |
| 71 | + #[prop_or_default] |
| 72 | + pub on_click: Callback<MouseEvent>, |
| 73 | + |
| 74 | + #[prop_or("#000000".into())] |
| 75 | + pub dark_color: AttrValue, |
| 76 | + #[prop_or("#ffffff".into())] |
| 77 | + pub light_color: AttrValue, |
| 78 | + |
| 79 | + /// set this to `false` if you disabled event bubbling as an optimization |
| 80 | + /// see https://yew.rs/docs/concepts/html/events#event-bubbling |
| 81 | + #[prop_or(true)] |
| 82 | + pub event_bubbling: bool, |
| 83 | +} |
| 84 | + |
| 85 | +#[function_component] |
| 86 | +pub fn QrCode(props: &Props) -> Html { |
| 87 | + let code = crate::QrCode::with_version(&*props.url, props.version, props.ec_level).unwrap(); |
| 88 | + let SvgAttrs { width, height, fg, bg, d } = |
| 89 | + code.render().dark_color(Color(&props.dark_color)).light_color(Color(&props.light_color)).build(); |
| 90 | + |
| 91 | + html! { |
| 92 | + <svg |
| 93 | + style={format!("height: {}; aspect-ratio: 1 / 1;", props.size)} |
| 94 | + onclick={props.on_click.clone()} |
| 95 | + xmlns="http://www.w3.org/2000/svg" |
| 96 | + version="1.1" |
| 97 | + viewBox={format!("0 0 {width} {height}")} |
| 98 | + shape-rendering="crispEdges" |
| 99 | + > |
| 100 | + <path |
| 101 | + d={format!("M0 0h{width}v{height}H0z")} |
| 102 | + fill={bg} |
| 103 | + onclick={props.event_bubbling.not().then(|| props.on_click.clone())} |
| 104 | + /> |
| 105 | + <path fill={fg} {d} onclick={props.event_bubbling.not().then(|| props.on_click.clone())} /> |
| 106 | + </svg> |
| 107 | + } |
| 108 | +} |
0 commit comments