use crate::theme::Theme;
use crate::widgets::{
Button,
secure_text_edit::{SecureTextEdit, SecureTextEditOutput, TextBuffer},
};
use egui::*;
use egui_lucide::Lucide;
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
use super::QRScanner;
use secure_types::SecureString;
#[derive(Clone)]
pub struct SecureInputField {
open: bool,
pub(crate) text: SecureString,
text_hidden: bool,
id: &'static str,
icon_size: Vec2,
min_size: Vec2,
inner_margin: Option<Margin>,
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
qr_scanner: QRScanner,
qr_enabled: bool,
}
impl SecureInputField {
pub fn new(id: &'static str, text_hidden: bool, open: bool) -> Self {
Self {
open,
text: SecureString::new_with_capacity(32).unwrap(),
text_hidden,
id,
icon_size: vec2(20.0, 20.0),
min_size: vec2(300.0, 20.0),
inner_margin: None,
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
qr_scanner: QRScanner::new(),
qr_enabled: true,
}
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn open(&mut self) {
self.open = true;
}
pub fn close(&mut self) {
self.open = false;
}
pub fn erase(&mut self) {
self.text.erase();
}
pub fn icon_size(mut self, size: Vec2) -> Self {
self.icon_size = size;
self
}
pub fn with_min_size(mut self, size: Vec2) -> Self {
self.min_size = size;
self
}
pub fn min_size(&self) -> Vec2 {
self.min_size
}
pub fn inner_margin(mut self, margin: Margin) -> Self {
self.inner_margin = Some(margin);
self
}
pub fn qr_enabled(mut self, enabled: bool) -> Self {
self.qr_enabled = enabled;
self
}
pub fn is_text_hidden(&self) -> bool {
self.text_hidden
}
pub fn text(&self) -> SecureString {
self.text.clone()
}
pub fn set_id(&mut self, id: &'static str) {
self.id = id;
}
pub fn set_text_hidden(&mut self, text_hidden: bool) {
self.text_hidden = text_hidden;
}
pub fn set_text(&mut self, text: SecureString) {
self.text = text;
}
pub fn set_min_size(&mut self, size: Vec2) {
self.min_size = size;
}
pub fn set_icon_size(&mut self, size: Vec2) {
self.icon_size = size;
}
pub fn set_inner_margin(&mut self, margin: Margin) {
self.inner_margin = Some(margin);
}
pub fn enable_qr_scanner(&mut self) {
self.qr_enabled = true;
}
pub fn disable_qr_scanner(&mut self) {
self.qr_enabled = false;
}
pub fn show(&mut self, ui: &mut Ui) -> Option<SecureTextEditOutput> {
if !self.open {
return None;
}
let mut hidden = self.is_text_hidden();
let (output, qr_clicked) = show_field_chrome(
ui,
self.id,
&mut self.text,
Some(&mut hidden),
self.min_size,
self.inner_margin,
WidgetText::default(),
self.icon_size,
self.qr_enabled,
);
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
{
if let Some(res) = drive_qr_scanner(
&mut self.qr_scanner,
ui.ctx(),
qr_clicked,
self.qr_enabled,
) {
self.set_text(res);
}
}
#[cfg(not(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
)))]
let _ = qr_clicked;
self.set_text_hidden(hidden);
Some(output)
}
}
pub struct InputField {
open: bool,
text: String,
id: &'static str,
icon_size: Vec2,
min_size: Vec2,
inner_margin: Option<Margin>,
hint_text: WidgetText,
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
qr_scanner: QRScanner,
qr_enabled: bool,
}
impl InputField {
pub fn new(id: &'static str, open: bool) -> Self {
Self {
open,
text: String::new(),
id,
icon_size: vec2(20.0, 20.0),
min_size: vec2(300.0, 20.0),
inner_margin: None,
hint_text: WidgetText::default(),
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
qr_scanner: QRScanner::new(),
qr_enabled: true,
}
}
pub fn hint_text(mut self, hint_text: impl Into<WidgetText>) -> Self {
self.hint_text = hint_text.into();
self
}
pub fn set_hint_text(&mut self, hint_text: impl Into<WidgetText>) {
self.hint_text = hint_text.into();
}
pub fn is_open(&self) -> bool {
self.open
}
pub fn open(&mut self) {
self.open = true;
}
pub fn close(&mut self) {
self.open = false;
self.close_qr_scanner();
}
pub fn close_qr_scanner(&mut self) {
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
self.qr_scanner.close();
}
pub fn erase(&mut self) {
self.text.clear();
}
pub fn text(&self) -> &str {
&self.text
}
pub fn set_text(&mut self, text: &str) {
self.text.clear();
self.text.push_str(text);
}
pub fn icon_size(mut self, size: Vec2) -> Self {
self.icon_size = size;
self
}
pub fn with_min_size(mut self, size: Vec2) -> Self {
self.min_size = size;
self
}
pub fn min_size(&self) -> Vec2 {
self.min_size
}
pub fn inner_margin(mut self, margin: Margin) -> Self {
self.inner_margin = Some(margin);
self
}
pub fn set_id(&mut self, id: &'static str) {
self.id = id;
}
pub fn set_min_size(&mut self, size: Vec2) {
self.min_size = size;
}
pub fn set_icon_size(&mut self, size: Vec2) {
self.icon_size = size;
}
pub fn set_inner_margin(&mut self, margin: Margin) {
self.inner_margin = Some(margin);
}
pub fn qr_enabled(mut self, enabled: bool) -> Self {
self.qr_enabled = enabled;
self
}
pub fn enable_qr_scanner(&mut self) {
self.qr_enabled = true;
}
pub fn disable_qr_scanner(&mut self) {
self.qr_enabled = false;
}
pub fn show(&mut self, ui: &mut Ui) -> Option<SecureTextEditOutput> {
if !self.open {
return None;
}
let (output, qr_clicked) = show_field_chrome(
ui,
self.id,
&mut self.text,
None,
self.min_size,
self.inner_margin,
self.hint_text.clone(),
self.icon_size,
self.qr_enabled,
);
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
{
if let Some(res) = drive_qr_scanner(
&mut self.qr_scanner,
ui.ctx(),
qr_clicked,
self.qr_enabled,
) {
let decoded = res.unlock_str(|s| s.to_owned());
self.set_text(&decoded);
}
}
#[cfg(not(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
)))]
let _ = qr_clicked;
Some(output)
}
}
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
fn drive_qr_scanner(
scanner: &mut QRScanner,
ctx: &Context,
clicked: bool,
enabled: bool,
) -> Option<SecureString> {
if !enabled {
return None;
}
if clicked {
scanner.open(ctx.clone());
}
scanner.show(ctx);
if let Some(res) = scanner.take_result() {
scanner.reset();
Some(res)
} else {
None
}
}
#[allow(clippy::too_many_arguments)]
fn show_field_chrome(
ui: &mut Ui,
id: &'static str,
text: &mut dyn TextBuffer,
mut hidden: Option<&mut bool>,
min_size: Vec2,
inner_margin: Option<Margin>,
hint_text: WidgetText,
icon_size: Vec2,
qr_enabled: bool,
) -> (SecureTextEditOutput, bool) {
let theme = Theme::current(ui.ctx());
if !id.is_empty() {
ui.label(RichText::new(id).size(theme.typography.large));
}
let margin = inner_margin.unwrap_or_else(|| Margin::same(theme.inner_margin));
let row_height = (theme.typography.normal + margin.sum().y).max(min_size.y);
let row_size = vec2(min_size.x, row_height);
let password = hidden.as_deref().copied().unwrap_or(false);
let text_edit = SecureTextEdit::singleline(text)
.min_size(min_size)
.margin(margin)
.hint_text(hint_text)
.password(password)
.font(FontId::proportional(theme.typography.normal));
#[cfg_attr(
not(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
)),
allow(unused_mut)
)]
let mut qr_clicked = false;
let output = ui.allocate_ui_with_layout(
row_size,
Layout::left_to_right(Align::Center),
|ui| {
let output = text_edit.show(ui);
let text_color = theme.colors.text;
if let Some(state) = hidden.as_deref_mut() {
let icon = if *state { Lucide::EyeOff } else { Lucide::Eye };
let img = icon.size(icon_size.x).color(text_color).image();
if ui.add(Button::image(img)).clicked() {
*state = !*state;
}
}
#[cfg(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
))]
{
if qr_enabled {
let icon = Lucide::ScanQrCode;
let img = icon.size(icon_size.x).color(text_color).image();
if ui.add(Button::image(img)).clicked() {
qr_clicked = true;
}
}
}
output
},
);
#[cfg(not(all(
feature = "qr-scanner",
any(target_os = "linux", target_os = "windows")
)))]
let _ = qr_enabled;
(output.inner, qr_clicked)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn input_field_drives_a_plain_string() {
let mut field = InputField::new("Name", true);
field.set_text("hello");
assert_eq!(field.text(), "hello");
field.erase();
assert_eq!(field.text(), "");
}
}