use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use crate::core::{Color, Rect};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
const MATRIX_SIZE: u32 = 21;
pub struct QRCode {
base: BaseWidget,
data: String,
module_size: u32,
quiet_zone: u32,
}
impl QRCode {
pub fn new(geometry: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::QRCode, geometry, "QRCode"),
data: String::new(),
module_size: 4,
quiet_zone: 2,
}
}
pub fn data(&self) -> &str {
&self.data
}
pub fn set_data(&mut self, data: &str) {
self.data = data.to_string();
self.base.request_redraw();
}
pub fn module_size(&self) -> u32 {
self.module_size
}
pub fn set_module_size(&mut self, size: u32) {
self.module_size = size.max(1);
self.base.request_redraw();
}
fn generate_matrix(&self) -> [[bool; MATRIX_SIZE as usize]; MATRIX_SIZE as usize] {
let mut matrix = [[false; MATRIX_SIZE as usize]; MATRIX_SIZE as usize];
let mut hasher = DefaultHasher::new();
self.data.hash(&mut hasher);
let seed = hasher.finish();
let mut state = seed;
for row in 0..MATRIX_SIZE {
for col in 0..MATRIX_SIZE {
let in_finder = row < 7 && !(7..MATRIX_SIZE - 7).contains(&col)
|| row >= MATRIX_SIZE - 7 && col < 7;
if in_finder {
let is_outer = row == 0 || row == 6 || col == 0 || col == 6;
let is_inner = (2..=4).contains(&row) && (2..=4).contains(&col);
let _is_sep = row == 7
|| col == 7
|| (row < 7 && col == MATRIX_SIZE - 8)
|| (row == MATRIX_SIZE - 8 && col < 7);
matrix[row as usize][col as usize] = is_outer || is_inner;
continue;
}
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
matrix[row as usize][col as usize] = (state >> ((col % 8) * 8)) & 1 == 1;
}
}
matrix
}
}
impl Widget for QRCode {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
}
impl Draw for QRCode {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.geometry();
let module = self.module_size.max(1);
let total_modules = MATRIX_SIZE + self.quiet_zone * 2;
let total_pixels = total_modules * module;
let offset_x = rect.x + (rect.width.saturating_sub(total_pixels) / 2) as i32;
let offset_y = rect.y + (rect.height.saturating_sub(total_pixels) / 2) as i32;
let bg_rect = Rect::new(
offset_x.max(rect.x),
offset_y.max(rect.y),
total_pixels.min(rect.width),
total_pixels.min(rect.height),
);
context.fill_rect(bg_rect, Color::WHITE);
let matrix = self.generate_matrix();
for row in 0..MATRIX_SIZE {
for col in 0..MATRIX_SIZE {
let x = offset_x + (self.quiet_zone + col) as i32 * module as i32;
let y = offset_y + (self.quiet_zone + row) as i32 * module as i32;
if x + module as i32 <= rect.x
|| x >= rect.x + rect.width as i32
|| y + module as i32 <= rect.y
|| y >= rect.y + rect.height as i32
{
continue;
}
let cell_w = module.min((rect.x + rect.width as i32 - x).max(0) as u32);
let cell_h = module.min((rect.y + rect.height as i32 - y).max(0) as u32);
if cell_w == 0 || cell_h == 0 {
continue;
}
if matrix[row as usize][col as usize] {
let cell_rect = Rect::new(x, y, cell_w, cell_h);
context.fill_rect(cell_rect, Color::BLACK);
}
}
}
}
}
impl EventHandler for QRCode {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Point;
#[test]
fn qr_code_creation() {
let qr = QRCode::new(Rect::new(0, 0, 120, 120));
assert!(qr.data().is_empty());
assert_eq!(qr.module_size(), 4);
assert_eq!(qr.kind(), WidgetKind::QRCode);
}
#[test]
fn qr_code_set_data() {
let mut qr = QRCode::new(Rect::new(0, 0, 120, 120));
qr.set_data("Hello, World!");
assert_eq!(qr.data(), "Hello, World!");
}
#[test]
fn qr_code_set_module_size() {
let mut qr = QRCode::new(Rect::new(0, 0, 120, 120));
assert_eq!(qr.module_size(), 4);
qr.set_module_size(8);
assert_eq!(qr.module_size(), 8);
qr.set_module_size(0); assert_eq!(qr.module_size(), 1);
}
#[test]
fn qr_code_deterministic_output() {
let mut qr1 = QRCode::new(Rect::new(0, 0, 120, 120));
qr1.set_data("test-data");
let mut qr2 = QRCode::new(Rect::new(0, 0, 120, 120));
qr2.set_data("test-data");
let m1 = qr1.generate_matrix();
let m2 = qr2.generate_matrix();
assert_eq!(m1, m2);
}
#[test]
fn qr_code_different_data_different_output() {
let mut qr1 = QRCode::new(Rect::new(0, 0, 120, 120));
qr1.set_data("data-a");
let mut qr2 = QRCode::new(Rect::new(0, 0, 120, 120));
qr2.set_data("data-b");
let m1 = qr1.generate_matrix();
let m2 = qr2.generate_matrix();
assert_ne!(m1, m2, "different data should produce different matrices");
}
#[test]
fn qr_code_svg_output() {
let mut qr = QRCode::new(Rect::new(0, 0, 100, 100));
qr.set_data("QR SVG Test");
let svg = crate::widget::svg::render_to_svg(&mut qr);
assert!(svg.starts_with("<svg"));
assert!(svg.ends_with("</svg>"));
assert!(svg.contains("fill="));
}
#[test]
fn qr_code_event_handler_delegates() {
let mut qr = QRCode::new(Rect::new(0, 0, 100, 100));
qr.handle_event(&Event::MouseMove { pos: Point::new(10, 10) });
qr.handle_event(&Event::KeyDown((65, 0)));
}
}