Misc · Codes and checks: a QR code scanner and a captcha
misc::QrCodeScanner reads QR codes with rqrr 0.11 from the frames the host hands in, one at a time off the main thread, and hands each code to the owner once. misc::Captcha shows the owner's picture over an answer field that empties and keeps focus with each answer; the owner says how it stood. BarcodeDisplay, Signature and Rating Widget point to their homes.
This commit is contained in:
1 parent
dc4911a68f
commit
692fae6244
14 files changed
+785
-10
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,99 @@
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
|
||||
use ely_gpui_component::misc::{Captcha, CaptchaState, QrCodeScanner};
|
||||
use gpui::{App, IntoElement, ParentElement, RenderImage, Styled, Window, div, px};
|
||||
|
||||
use crate::probe::probe;
|
||||
use crate::ui::section;
|
||||
|
||||
const ANSWERS: [&str; 2] = ["W7XK", "Q3NP"];
|
||||
const PICTURES: &str = concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/examples/gallery/assets/captcha"
|
||||
);
|
||||
|
||||
/// The frame a camera would hand in: a QR code on a card over a dim scene, in gpui's BGRA pixels.
|
||||
fn scene() -> Arc<RenderImage> {
|
||||
let code = qrcode::QrCode::new("https://example.com/ely").expect("the link fits a QR code");
|
||||
let (modules, scale, quiet) = (code.width() as u32, 4, 4);
|
||||
let (width, height, side) = (360, 240, (modules + quiet * 2) * scale);
|
||||
let (left, top) = ((width - side) / 2, (height - side) / 2);
|
||||
let mut pixels = image::RgbaImage::from_fn(width, height, |x, y| {
|
||||
let shade = (60 + (x + y) / 10) as u8;
|
||||
image::Rgba([shade + 12, shade + 6, shade, 255])
|
||||
});
|
||||
let dark: Vec<bool> = code
|
||||
.to_colors()
|
||||
.into_iter()
|
||||
.map(|color| color == qrcode::Color::Dark)
|
||||
.collect();
|
||||
for (x, y) in (0..side).flat_map(|x| (0..side).map(move |y| (x, y))) {
|
||||
let module = (x / scale)
|
||||
.checked_sub(quiet)
|
||||
.zip((y / scale).checked_sub(quiet));
|
||||
let ink = module
|
||||
.filter(|(mx, my)| *mx < modules && *my < modules)
|
||||
.is_some_and(|(mx, my)| dark[(my * modules + mx) as usize]);
|
||||
let shade = if ink { 20 } else { 245 };
|
||||
pixels.put_pixel(left + x, top + y, image::Rgba([shade, shade, shade, 255]));
|
||||
}
|
||||
Arc::new(RenderImage::new(vec![image::Frame::new(pixels)]))
|
||||
}
|
||||
|
||||
pub fn render(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let frame = window.use_keyed_state("scanner-scene", cx, |_, _| scene());
|
||||
let desk = window.use_keyed_state("captcha-desk", cx, |_, _| (0, CaptchaState::Asking));
|
||||
let (ix, state) = *desk.read(cx);
|
||||
let answer = ANSWERS[ix];
|
||||
let (checked, refreshed) = (desk.clone(), desk);
|
||||
let picture = PathBuf::from(format!("{PICTURES}/{}.png", answer.to_lowercase()));
|
||||
div()
|
||||
.child(
|
||||
section(
|
||||
"QRCodeScanner",
|
||||
"A camera's view with a square to aim at, reading QR codes off the main thread from frames the host hands in. gpui has no camera, so the gallery hands in one frame of its own.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div().w(px(280.0)).child(
|
||||
QrCodeScanner::new("scanner", 1.5)
|
||||
.frame(frame.read(cx).clone())
|
||||
.on_scan(|text, _, _| log::info!("gallery: scanned {text}")),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
section(
|
||||
"Captcha",
|
||||
"A picture of characters to copy, from the owner, who checks the answer and says how it stood; the refresh asks for another picture.",
|
||||
cx,
|
||||
)
|
||||
.child(probe(
|
||||
"captcha",
|
||||
div().w(px(280.0)).child(
|
||||
Captcha::new("captcha", picture, 3.0)
|
||||
.state(state)
|
||||
.on_answer(move |text, _, cx| {
|
||||
checked.update(cx, |desk, cx| {
|
||||
desk.1 = match text.eq_ignore_ascii_case(answer) {
|
||||
true => CaptchaState::Passed,
|
||||
false => CaptchaState::Wrong,
|
||||
};
|
||||
cx.notify();
|
||||
})
|
||||
})
|
||||
.on_refresh(move |_, cx| {
|
||||
refreshed.update(cx, |desk, cx| {
|
||||
*desk = ((desk.0 + 1) % ANSWERS.len(), CaptchaState::Asking);
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
),
|
||||
)),
|
||||
)
|
||||
.child(section(
|
||||
"BarcodeDisplay · Signature · Rating Widget",
|
||||
"Each has one home, shown on its own chapter's page: BarcodeDisplay is data_display::Barcode, Signature is forms::SignaturePad, and Rating Widget is forms::Rating.",
|
||||
cx,
|
||||
))
|
||||
}
|
||||
@@ -7,11 +7,13 @@ use crate::probe::probe;
|
||||
use crate::script::Step;
|
||||
use crate::ui::{section, specimen, specimens};
|
||||
|
||||
mod codes;
|
||||
|
||||
pub const PAGE: Page = Page {
|
||||
number: 42,
|
||||
slug: "misc",
|
||||
title: "Misc",
|
||||
summary: "Small tools that stand alone: clocks, a stopwatch, a calculator, a unit converter.",
|
||||
summary: "Small tools that stand alone: clocks, a stopwatch, a calculator, a unit converter, a QR code scanner, a captcha.",
|
||||
render,
|
||||
script: &[
|
||||
Step::DownAt("stopwatch-start", 30.0, 62.0),
|
||||
@@ -32,6 +34,14 @@ pub const PAGE: Page = Page {
|
||||
Step::Key("cmd-a"),
|
||||
Step::Type("1000000000000000"),
|
||||
Step::Shot("converter-long"),
|
||||
Step::DownAt("captcha", 24.0, 104.0),
|
||||
Step::UpAt("captcha", 24.0, 104.0),
|
||||
Step::Type("abcd"),
|
||||
Step::Key("enter"),
|
||||
Step::Shot("captcha-wrong"),
|
||||
Step::Type("w7xk"),
|
||||
Step::Key("enter"),
|
||||
Step::Shot("captcha-passed"),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -39,12 +49,13 @@ fn zone(name: &str) -> TimeZone {
|
||||
TimeZone::get(name).unwrap_or_else(|error| panic!("the zone {name}: {error}"))
|
||||
}
|
||||
|
||||
fn render(_: &mut Window, cx: &mut App) -> AnyElement {
|
||||
fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
div()
|
||||
.child(clocks(cx))
|
||||
.child(stopwatch(cx))
|
||||
.child(calculator(cx))
|
||||
.child(converter(cx))
|
||||
.child(codes::render(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user