use revue::prelude::*;
use revue::widget::{qrcode, ErrorCorrection, QrStyle};
struct QrGenerator {
url: Signal<String>,
style_idx: Signal<usize>,
cursor: Signal<usize>,
}
impl QrGenerator {
fn new() -> Self {
let default_url = String::from("https://example.com");
let len = default_url.len();
Self {
url: signal(default_url),
style_idx: signal(0),
cursor: signal(len),
}
}
fn styles() -> &'static [(QrStyle, &'static str)] {
&[
(QrStyle::HalfBlock, "Half Block"),
(QrStyle::Braille, "Braille"),
(QrStyle::FullBlock, "Full Block"),
(QrStyle::Ascii, "ASCII"),
]
}
fn handle_key(&self, key_event: &KeyEvent) -> bool {
match (&key_event.key, key_event.ctrl) {
(Key::Char('u'), true) => {
self.url.set(String::new());
self.cursor.set(0);
true
}
(Key::Char(c), false) if !c.is_control() => {
let ch = *c;
let cur = self.cursor.get();
self.url.update(|s| s.insert(cur, ch));
self.cursor.update(|c| *c += 1);
true
}
(Key::Backspace, _) => {
let cur = self.cursor.get();
if cur > 0 {
self.url.update(|s| {
s.remove(cur - 1);
});
self.cursor.update(|c| *c -= 1);
}
true
}
(Key::Delete, _) => {
let cur = self.cursor.get();
let len = self.url.get().len();
if cur < len {
self.url.update(|s| {
s.remove(cur);
});
}
true
}
(Key::Left, _) => {
self.cursor.update(|c| {
if *c > 0 {
*c -= 1;
}
});
true
}
(Key::Right, _) => {
let len = self.url.get().len();
self.cursor.update(|c| {
if *c < len {
*c += 1;
}
});
true
}
(Key::Home, _) => {
self.cursor.set(0);
true
}
(Key::End, _) => {
let len = self.url.get().len();
self.cursor.set(len);
true
}
(Key::Tab, _) => {
let len = Self::styles().len();
self.style_idx.update(|v| *v = (*v + 1) % len);
true
}
(Key::BackTab, _) => {
let len = Self::styles().len();
self.style_idx.update(|v| *v = (*v + len - 1) % len);
true
}
_ => false,
}
}
}
impl View for QrGenerator {
fn render(&self, ctx: &mut RenderContext) {
let url = self.url.get();
let cursor = self.cursor.get();
let idx = self.style_idx.get();
let (style, style_name) = Self::styles()[idx];
let input_display = if url.is_empty() {
"Enter a URL...".to_string()
} else {
let pos = cursor.min(url.len());
let (before, after) = url.split_at(pos);
format!("{}\u{2502}{}", before, after)
};
let input_color = if url.is_empty() {
Color::hex(0x666666)
} else {
Color::WHITE
};
let title = Text::new(format!("QR Code Generator [{}]", style_name))
.bold()
.fg(Color::CYAN)
.align(Alignment::Center);
let input = Border::rounded()
.title("URL")
.fg(Color::GREEN)
.child(Text::new(input_display).fg(input_color));
let qr_box = if !url.is_empty() {
let qr = qrcode(&url)
.style(style)
.fg(Color::WHITE)
.bg(Color::BLACK)
.error_correction(ErrorCorrection::Medium)
.quiet_zone(1);
Border::rounded().title("QR").child(qr)
} else {
Border::rounded().title("QR").child(
Text::new("Type a URL to generate QR code")
.fg(Color::hex(0x666666))
.align(Alignment::Center),
)
};
let controls = Border::single().title("Controls").child(
hstack()
.gap(3)
.child(Text::muted("[Tab] Style"))
.child(Text::muted("[Ctrl+U] Clear"))
.child(Text::muted("[Esc] Quit")),
);
let main = vstack()
.gap(1)
.child_sized(title, 1)
.child_sized(input, 3)
.child(qr_box) .child_sized(controls, 3);
main.render(ctx);
}
fn meta(&self) -> WidgetMeta {
WidgetMeta::new("QrGenerator")
}
}
fn main() -> Result<()> {
let mut app = App::builder().build();
let generator = QrGenerator::new();
app.run(generator, |event, gen, app| match event {
Event::Key(key_event) => match key_event.key {
Key::Escape => {
app.quit();
false
}
_ => gen.handle_key(key_event),
},
Event::Paste(text) => {
let cur = gen.cursor.get();
gen.url.update(|s| s.insert_str(cur, text));
gen.cursor.update(|c| *c += text.len());
true
}
_ => false,
})
}