From e067a0a9003fc0fde1f0ef373f8c0fd1819458c2 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Mon, 2 Jun 2025 21:03:49 +0300 Subject: [PATCH] qr: add max size support, ui copy button --- src/gui/views/qr.rs | 187 ++++++++++++++++++++++++++------------------ 1 file changed, 113 insertions(+), 74 deletions(-) diff --git a/src/gui/views/qr.rs b/src/gui/views/qr.rs index 6be3cd2a..e5288adc 100644 --- a/src/gui/views/qr.rs +++ b/src/gui/views/qr.rs @@ -12,27 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. +use egui::epaint::RectShape; +use egui::{SizeHint, TextureHandle, UiBuilder}; +use image::codecs::png::{CompressionType, FilterType, PngEncoder}; +use image::{ExtendedColorType, ImageEncoder}; +use parking_lot::RwLock; +use qrcodegen::QrCode; use std::mem::size_of; use std::sync::Arc; -use parking_lot::RwLock; use std::thread; -use egui::{SizeHint, TextureHandle, UiBuilder}; -use egui::epaint::RectShape; -use image::{ExtendedColorType, ImageEncoder}; -use image::codecs::png::{CompressionType, FilterType, PngEncoder}; -use qrcodegen::QrCode; -use crate::gui::Colors; -use crate::gui::icons::IMAGES_SQUARE; +use crate::gui::icons::{COPY, IMAGES_SQUARE}; use crate::gui::platform::PlatformCallbacks; use crate::gui::views::types::QrImageState; use crate::gui::views::View; +use crate::gui::Colors; /// QR code image from text. pub struct QrCodeContent { /// QR code text. text: String, + /// Maximum QR code size. + max_size: f32, + /// Flag to draw animated QR with Uniform Resources /// https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md animated: bool, @@ -53,6 +56,7 @@ impl QrCodeContent { pub fn new(text: String, animated: bool) -> Self { Self { text, + max_size: DEFAULT_QR_SIZE as f32, animated, animated_index: None, animation_time: None, @@ -61,6 +65,12 @@ impl QrCodeContent { } } + /// Setup maximum QR code size. + pub fn with_max_size(mut self, max_size: f32) -> Self { + self.max_size = max_size; + self + } + /// Draw QR code. pub fn ui(&mut self, ui: &mut egui::Ui, cb: &dyn PlatformCallbacks) { if self.animated { @@ -70,6 +80,9 @@ impl QrCodeContent { // Show static QR code. self.static_ui(ui, cb); } + ui.add_space(8.0); + View::horizontal_line(ui, Colors::item_stroke()); + ui.add_space(8.0); } /// Draw animated QR code content. @@ -120,18 +133,32 @@ impl QrCodeContent { r_state.exporting || r_state.gif_creating }; if !sharing { - // Show button to share QR. - let share_text = format!("{} {}", IMAGES_SQUARE, t!("share")); - View::colored_text_button(ui, - share_text, - Colors::blue(), - Colors::white_or_black(false), || { - { - let mut w_state = self.qr_image_state.write(); - w_state.exporting = true; - } - // Create GIF to export. - self.create_qr_gif(); + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + // Draw copy button. + let copy_text = format!("{} {}", COPY, t!("copy")); + View::button(ui, copy_text, Colors::white_or_black(false), || { + cb.copy_string_to_buffer(self.text.clone()); + }); + }); + columns[1].vertical_centered_justified(|ui| { + // Show button to share QR. + let share_text = format!("{} {}", IMAGES_SQUARE, t!("share")); + View::colored_text_button(ui, + share_text, + Colors::blue(), + Colors::white_or_black(false), || { + { + let mut w_state = self.qr_image_state.write(); + w_state.exporting = true; + } + // Create GIF to export. + self.create_qr_gif(); + }); + }); }); } else { ui.vertical_centered(|ui| { @@ -141,10 +168,6 @@ impl QrCodeContent { }); } - ui.add_space(8.0); - View::horizontal_line(ui, Colors::item_stroke()); - ui.add_space(8.0); - // Check if GIF was created to share. let has_gif = { let r_state = self.qr_image_state.read(); @@ -197,66 +220,82 @@ impl QrCodeContent { View::ellipsize_text(ui, self.text.clone(), 16.0, Colors::inactive_text()); ui.add_space(6.0); - // Show button to share QR. - ui.vertical_centered(|ui| { - let share_text = format!("{} {}", IMAGES_SQUARE, t!("share")); - View::colored_text_button(ui, - share_text, - Colors::blue(), - Colors::white_or_black(false), || { - let text = self.text.as_str(); - if let Ok(qr) = QrCode::encode_text(text, qrcodegen::QrCodeEcc::Low) { - if let Some(data) = Self::qr_to_image_data(qr, DEFAULT_QR_SIZE as usize) { - let mut png = vec![]; - let png_enc = PngEncoder::new_with_quality(&mut png, - CompressionType::Best, - FilterType::NoFilter); - if let Ok(()) = png_enc.write_image(data.as_slice(), - DEFAULT_QR_SIZE, - DEFAULT_QR_SIZE, - ExtendedColorType::L8) { - let name = format!("{}.png", chrono::Utc::now().timestamp()); - cb.share_data(name, png).unwrap_or_default(); - } - } - } + // Setup spacing between buttons. + ui.spacing_mut().item_spacing = egui::Vec2::new(6.0, 0.0); + + ui.columns(2, |columns| { + columns[0].vertical_centered_justified(|ui| { + // Draw copy button. + let copy_text = format!("{} {}", COPY, t!("copy")); + View::button(ui, copy_text, Colors::white_or_black(false), || { + cb.copy_string_to_buffer(self.text.clone()); + }); + }); + columns[1].vertical_centered_justified(|ui| { + let share_text = format!("{} {}", IMAGES_SQUARE, t!("share")); + View::colored_text_button(ui, + share_text, + Colors::blue(), + Colors::white_or_black(false), || { + self.share_static(cb); + }); }); }); - ui.add_space(8.0); - View::horizontal_line(ui, Colors::item_stroke()); - ui.add_space(8.0); + } + } + + /// Share static QR code image. + fn share_static(&self, cb: &dyn PlatformCallbacks) { + let text = self.text.as_str(); + if let Ok(qr) = QrCode::encode_text(text, qrcodegen::QrCodeEcc::Low) { + let size = DEFAULT_QR_SIZE as usize; + if let Some(data) = Self::qr_to_image_data(qr, size) { + let mut png = vec![]; + let png_enc = PngEncoder::new_with_quality(&mut png, + CompressionType::Best, + FilterType::NoFilter); + if let Ok(()) = png_enc.write_image(data.as_slice(), + DEFAULT_QR_SIZE, + DEFAULT_QR_SIZE, + ExtendedColorType::L8) { + let name = format!("{}.png", chrono::Utc::now().timestamp()); + cb.share_data(name, png).unwrap_or_default(); + } + } } } /// Draw QR code image content. fn qr_image_ui(&mut self, svg: Vec, ui: &mut egui::Ui) { - let mut rect = ui.available_rect_before_wrap(); - rect.min += egui::emath::vec2(10.0, 0.0); - rect.max -= egui::emath::vec2(10.0, 0.0); + View::max_width_ui(ui, self.max_size, |ui| { + let mut rect = ui.available_rect_before_wrap(); + rect.min += egui::emath::vec2(10.0, 0.0); + rect.max -= egui::emath::vec2(10.0, 0.0); - // Create background shape. - let mut bg_shape = RectShape::new( - rect, - egui::CornerRadius::default(), - egui::Color32::WHITE, - egui::Stroke::NONE, - egui::StrokeKind::Outside - ); - let bg_idx = ui.painter().add(bg_shape.clone()); + // Create background shape. + let mut bg_shape = RectShape::new( + rect, + egui::CornerRadius::default(), + egui::Color32::WHITE, + egui::Stroke::NONE, + egui::StrokeKind::Outside + ); + let bg_idx = ui.painter().add(bg_shape.clone()); - // Draw QR code image content. - let mut content_rect = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| { - ui.add_space(10.0); - let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32); - self.texture_handle = Some(View::svg_image(ui, "qr_code", svg.as_slice(), Some(size))); - ui.add_space(10.0); - }).response.rect; + // Draw QR code image. + let mut content_rect = ui.scope_builder(UiBuilder::new().max_rect(rect), |ui| { + ui.add_space(10.0); + let size = SizeHint::Size(ui.available_width() as u32, ui.available_width() as u32); + self.texture_handle = Some(View::svg_image(ui, "qr", svg.as_slice(), Some(size))); + ui.add_space(10.0); + }).response.rect; - // Setup background size. - content_rect.min -= egui::emath::vec2(10.0, 0.0); - content_rect.max += egui::emath::vec2(10.0, 0.0); - bg_shape.rect = content_rect; - ui.painter().set(bg_idx, bg_shape); + // Setup background size. + content_rect.min -= egui::emath::vec2(10.0, 0.0); + content_rect.max += egui::emath::vec2(10.0, 0.0); + bg_shape.rect = content_rect; + ui.painter().set(bg_idx, bg_shape); + }); } /// Check if QR code is loading.