From 20e17792e94fb068b1fad276d536c9dfd2c57d7e Mon Sep 17 00:00:00 2001 From: Crypto-Spartan Date: Sun, 12 Jan 2025 19:10:10 -0500 Subject: [PATCH] refactor: some simplification & better code organization, rewrote some small parts --- src/gui/app.rs | 199 ++++++++++++++-------------- src/gui/popup.rs | 294 ++++++++++++++++++----------------------- src/mac_address/mod.rs | 2 +- src/unifi/devices.rs | 2 +- 4 files changed, 232 insertions(+), 265 deletions(-) diff --git a/src/gui/app.rs b/src/gui/app.rs index f954da5..083aad1 100644 --- a/src/gui/app.rs +++ b/src/gui/app.rs @@ -67,7 +67,12 @@ impl<'a> eframe::App for GuiApp<'a> { popup_window_option, &mut gui_channels.search_info_tx, ); - let main_window_size: egui::Vec2 = ui.available_size(); + + let main_window_size: egui::Pos2 = { + let window_coords = ctx.input(|i| i.viewport().inner_rect).unwrap(); + let next_widget_pos = ui.next_widget_position(); + egui::pos2(window_coords.width(), next_widget_pos.y) + }; GuiApp::handle_popup_window( ctx, popup_window_option, @@ -160,7 +165,6 @@ impl<'a> GuiApp<'a> { ui.selectable_value(font_size_enum, FontSize::ExtraLarge, "Extra Large"); }); }); - ui.add_space(150.); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.hyperlink_to( "Source Code", @@ -224,12 +228,14 @@ impl<'a> GuiApp<'a> { ui.checkbox(invalid_certs_checked, "Accept Invalid HTTPS Certificate"); // add "Search Unifi" button - GuiApp::create_search_button(ui, gui_input_fields, popup_window_option, search_info_tx); + ui.vertical_centered(|ui| { + if ui.button("Search Unifi").clicked() { + GuiApp::handle_button_click(gui_input_fields, popup_window_option, search_info_tx); + } + }); } - // add "Search Unifi" button - fn create_search_button( - ui: &mut egui::Ui, + fn handle_button_click( gui_input_fields: &mut GuiInputFields, popup_window_option: &mut Option, search_info_tx: &mut flume::Sender, @@ -244,110 +250,107 @@ impl<'a> GuiApp<'a> { ref remember_pass_checked, } = gui_input_fields; - ui.horizontal(|ui| { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - if ui.button("Search Unifi").clicked() { - - // if any fields are empty, display error - if username_input.is_empty() - || password_input.is_empty() - || server_url_input.is_empty() - || mac_addr_input.is_empty() { - *popup_window_option = Some(PopupWindow::Error( - GuiError::new_standard( - "Required Fields", - Box::from("Username, Password, Server URL, & MAC Address are all required fields.") - ) - )); - // if the mac address isn't in a valid format, display error - } else if !text_is_valid_mac(mac_addr_input.as_bytes()) { - *popup_window_option = Some(PopupWindow::Error( - GuiError::new_standard( - "Invalid MAC Address", - Box::from("MAC Address must be formatted like XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX with hexadecimal characters only.") - ) - )); - // other checks passed, run the search - } else { - *popup_window_option = Some(PopupWindow::SearchProgress(0.)); + // if any fields are empty, display error + if username_input.is_empty() + || password_input.is_empty() + || server_url_input.is_empty() + || mac_addr_input.is_empty() { + *popup_window_option = Some(PopupWindow::Error( + GuiError::new_standard( + "Required Fields", + Box::from("Username, Password, Server URL, & MAC Address are all required fields.") + ) + )); + // if the mac address isn't in a valid format, display error + } else if !text_is_valid_mac(mac_addr_input.as_bytes()) { + *popup_window_option = Some(PopupWindow::Error( + GuiError::new_standard( + "Invalid MAC Address", + Box::from("MAC Address must be formatted like XX:XX:XX:XX:XX:XX or XX-XX-XX-XX-XX-XX with hexadecimal characters only.") + ) + )); + // other checks passed, run the search + } else { + *popup_window_option = Some(PopupWindow::SearchProgress(0.)); - let username = username_input.to_string(); - // don't zeroize the password if remember password checkbox is checked - // password is always zeroized on the search thread immediately after authentication - let password = { - if *remember_pass_checked { - password_input.to_string() - } else { - let p = std::mem::take(password_input); - password_input.zeroize(); - p - } - }; - let server_url = server_url_input.strip_suffix('/').unwrap_or(server_url_input).to_string(); - let mac_to_search = MacAddress::try_from(mac_addr_input.as_ref()) - .expect("Mac Address validation failed"); // SAFETY: this should never error due to the check above - let accept_invalid_certs = *invalid_certs_checked; + let username = username_input.to_string(); + // don't zeroize the password if remember password checkbox is checked + // password is always zeroized on the search thread immediately after authentication + let password = { + if *remember_pass_checked { + password_input.to_string() + } else { + let p = std::mem::take(password_input); + password_input.zeroize(); + p + } + }; + let server_url = server_url_input.strip_suffix('/').unwrap_or(server_url_input).to_string(); + let mac_to_search = MacAddress::try_from(mac_addr_input.as_ref()) + .expect("Mac Address validation failed"); // SAFETY: this should never error due to the check above + let accept_invalid_certs = *invalid_certs_checked; - search_info_tx.send( - UnifiSearchInfo { - username, - password, - server_url, - mac_to_search, - accept_invalid_certs - } - ).expect("sending struct UnifiSearchInfo through channel search_info_tx should be successful"); - } + search_info_tx.send( + UnifiSearchInfo { + username, + password, + server_url, + mac_to_search, + accept_invalid_certs } - }); - }); + ).expect("sending struct UnifiSearchInfo through channel search_info_tx should be successful"); + } } fn handle_popup_window( ctx: &egui::Context, popup_window_option: &mut Option, - main_window_size: egui::Vec2, + main_window_size: egui::Pos2, mac_addr_input: &str, gui_channels: &mut ChannelsGuiThread, ) { - if let Some(popup_window) = popup_window_option.clone() { - let popup_metadata = { - let width = main_window_size.x * 0.7; - WindowMeta { - ctx, - width, - default_x_pos: (main_window_size.x / 2.) - (width / 2.), - default_y_pos: main_window_size.y * 0.15, - } - }; + if popup_window_option.is_none() { + return + } + let popup_window = popup_window_option.clone().unwrap(); + let popup_metadata = { + let width = main_window_size.x * 0.7; + let default_pos = egui::pos2(main_window_size.x / 2., main_window_size.y / 2.); + WindowMeta { + ctx, + width, + default_pos, + } + }; + // dbg!(&main_window_size); + // dbg!(&popup_metadata); - match popup_window { - PopupWindow::SearchProgress(percentage) => { - PopupWindow::create_search_progress( - popup_metadata, - popup_window_option, - percentage, - mac_addr_input, - gui_channels, - ); - } - PopupWindow::SearchResult(unifi_device) => { - PopupWindow::create_search_result( - popup_metadata, - popup_window_option, - unifi_device, - ); - } - PopupWindow::Error(error) => { - PopupWindow::create_error(popup_metadata, popup_window_option, error); - } - PopupWindow::DisplayCancel => { - PopupWindow::create_cancel( - popup_metadata, - popup_window_option, - &mut gui_channels.device_rx, - ); - } + match popup_window { + PopupWindow::SearchProgress(percentage) => { + PopupWindow::create_search_progress( + popup_metadata, + popup_window_option, + percentage, + mac_addr_input, + gui_channels, + ); + } + PopupWindow::SearchResult(unifi_device) => { + PopupWindow::create_search_result( + popup_metadata, + popup_window_option, + unifi_device, + ); + } + PopupWindow::Error(error) => { + PopupWindow::create_error(popup_metadata, popup_window_option, error); + } + PopupWindow::DisplayCancel => { + PopupWindow::create_cancel( + popup_metadata, + popup_window_option, + &mut gui_channels.device_rx, + ); } } } diff --git a/src/gui/popup.rs b/src/gui/popup.rs index 5e5daaa..3efbf6e 100644 --- a/src/gui/popup.rs +++ b/src/gui/popup.rs @@ -2,6 +2,7 @@ use crate::{ gui::{CancelSignal, ChannelsGuiThread}, unifi::{api::UnifiAPIError, devices::UnifiDeviceBasic, search::UnifiSearchResult}, }; +use egui::TextBuffer; use std::borrow::Cow; #[derive(Debug, Clone, PartialEq)] @@ -42,7 +43,7 @@ impl<'a> GuiError<'a> { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub(super) enum PopupWindow<'a> { SearchProgress(f32), SearchResult(UnifiDeviceBasic), @@ -51,119 +52,122 @@ pub(super) enum PopupWindow<'a> { } #[derive(Debug, Copy, Clone, PartialEq)] -pub(super) struct WindowMeta<'b> { - pub(super) ctx: &'b egui::Context, +pub(super) struct WindowMeta<'a> { + pub(super) ctx: &'a egui::Context, pub(super) width: f32, - pub(super) default_x_pos: f32, - pub(super) default_y_pos: f32, + pub(super) default_pos: egui::Pos2, } impl<'a> PopupWindow<'a> { + fn create_window(title: impl Into, default_width: f32, default_pos: egui::Pos2) -> egui::Window<'a> { + egui::Window::new(title) + .resizable(false) + .collapsible(false) + //.auto_sized() + .default_width(default_width) + .pivot(egui::Align2::CENTER_CENTER) + .fixed_pos(default_pos) + } + pub(super) fn create_search_progress( popup_metadata: WindowMeta, popup_window_option: &mut Option, - percentage: f32, + mut percentage: f32, mac_address: &str, gui_channels: &mut ChannelsGuiThread, ) { - // create progress bar - let progress_bar = egui::widgets::ProgressBar::new(percentage) - .show_percentage() - .animate(true); + // get percentage value from channel to update the progress bar + if let Ok(new_percentage) = gui_channels.percentage_rx.try_recv() { + *popup_window_option = Some(PopupWindow::SearchProgress(new_percentage)); + percentage = new_percentage; + } - egui::Window::new("Running Unifi Search") - .resizable(false) - .collapsible(false) - .default_width(popup_metadata.width) - .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + // create popup window + PopupWindow::create_window("Running Unifi Search", popup_metadata.width, popup_metadata.default_pos) .show(popup_metadata.ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::centered_and_justified(egui::Direction::TopDown), - |ui| { - ui.label(format!( - "Searching for Unifi device with MAC Address: {}", - mac_address - )); - }, - ); - }); - // get percentage value from channel to update the progress bar - if let Ok(new_percentage) = gui_channels.percentage_rx.try_recv() { - *popup_window_option = Some(PopupWindow::SearchProgress(new_percentage)); - } - // check channel to see if we have a search result - if let Ok(unifi_search_result) = gui_channels.device_rx.try_recv() { - match unifi_search_result { - Ok(unifi_search_option) => match unifi_search_option { - Some(unifi_device) => { - *popup_window_option = - Some(PopupWindow::SearchResult(unifi_device)); - } - None => { - *popup_window_option = - Some(PopupWindow::Error(GuiError::new_info( - "Device Not Found", - format!( - "Unable to find device with MAC Address {}", - mac_address - ) - .into_boxed_str(), - ))); - } - }, - Err(ref unifi_api_error) => { - *popup_window_option = match unifi_api_error { - UnifiAPIError::ClientError { source } => { - debug_assert!(source.is_builder()); - Some(PopupWindow::Error(GuiError::new_critical( - "Reqwest Client Error", - format!( - "Unable to Build Unifi Client\n{}\n{}", - unifi_api_error, source - ) - .into_boxed_str(), - ))) - } - UnifiAPIError::LoginAuthenticationError { url } => { - Some(PopupWindow::Error(GuiError::new_standard( - "Login Failed", - format!("Unable to login to {}\n{}", url, unifi_api_error) - .into_boxed_str(), - ))) - } - UnifiAPIError::ReqwestError { source } => { - Some(PopupWindow::Error(GuiError::new_standard( - "Unifi API Error", - format!("{}\n{}", unifi_api_error, source).into_boxed_str(), - ))) - } - UnifiAPIError::JsonError { source, .. } => { - Some(PopupWindow::Error(GuiError::new_critical( - "Json Parsing Error", - format!("{}\n{}", unifi_api_error, source).into_boxed_str(), - ))) - } - } - } - } - } + ui.vertical_centered(|ui| { + ui.label(format!( + "Searching for Unifi device with MAC Address: {}", + mac_address + )); - ui.add(progress_bar); + // create progress bar + let progress_bar = { + egui::widgets::ProgressBar::new(percentage) + .show_percentage() + .animate(true) + }; + ui.add(progress_bar); - // cancel button - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::centered_and_justified(egui::Direction::TopDown), - |ui| { - if ui.button("Cancel").clicked() { - gui_channels.signal_tx.send(CancelSignal).unwrap(); - *popup_window_option = Some(PopupWindow::DisplayCancel); - } - }, - ); + // cancel button + if ui.button("Cancel").clicked() { + gui_channels.signal_tx.send(CancelSignal).unwrap(); + *popup_window_option = Some(PopupWindow::DisplayCancel); + } }); }); + + // return if canceled + if *popup_window_option == Some(PopupWindow::DisplayCancel) { + return; + } + + // check channel to see if we have a search result + if let Ok(unifi_search_result) = gui_channels.device_rx.try_recv() { + match unifi_search_result { + Ok(unifi_search_option) => match unifi_search_option { + Some(unifi_device) => { + *popup_window_option = + Some(PopupWindow::SearchResult(unifi_device)); + } + None => { + *popup_window_option = + Some(PopupWindow::Error(GuiError::new_info( + "Device Not Found", + format!( + "Unable to find device with MAC Address {}", + mac_address + ) + .into_boxed_str(), + ))); + } + }, + Err(ref unifi_api_error) => { + *popup_window_option = match unifi_api_error { + UnifiAPIError::ClientError { source } => { + debug_assert!(source.is_builder()); + Some(PopupWindow::Error(GuiError::new_critical( + "Reqwest Client Error", + format!( + "Unable to Build Unifi Client\n{}\n{}", + unifi_api_error, source + ) + .into_boxed_str(), + ))) + } + UnifiAPIError::LoginAuthenticationError { url } => { + Some(PopupWindow::Error(GuiError::new_standard( + "Login Failed", + format!("Unable to login to {}\n{}", url, unifi_api_error) + .into_boxed_str(), + ))) + } + UnifiAPIError::ReqwestError { source } => { + Some(PopupWindow::Error(GuiError::new_standard( + "Unifi API Error", + format!("{}\n{}", unifi_api_error, source).into_boxed_str(), + ))) + } + UnifiAPIError::JsonError { source, .. } => { + Some(PopupWindow::Error(GuiError::new_critical( + "Json Parsing Error", + format!("{}\n{}", unifi_api_error, source).into_boxed_str(), + ))) + } + } + } + } + } } pub(super) fn create_search_result( @@ -183,19 +187,10 @@ impl<'a> PopupWindow<'a> { site, } = unifi_device; - egui::Window::new("Unifi Search Result") - .resizable(false) - .collapsible(false) - .default_width(popup_metadata.width) - .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + PopupWindow::create_window("Unifi Search Result", popup_metadata.width, popup_metadata.default_pos) .show(popup_metadata.ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::centered_and_justified(egui::Direction::TopDown), - |ui| { - ui.label("Successfully found device!"); - }, - ); + ui.vertical_centered(|ui| { + ui.label("Successfully found device!"); }); // grid of results, grid allows for spacing/formatting @@ -255,7 +250,7 @@ impl<'a> PopupWindow<'a> { } // add gateway mode if true - if gateway_mode == Some(true) { + if gateway_mode.is_some_and(|x| x) { PopupWindow::create_search_result_row( // custom state.as_str implementation ui, "Gateway Mode:", "True", @@ -264,7 +259,9 @@ impl<'a> PopupWindow<'a> { }); // close button - PopupWindow::create_close_button(ui, popup_window_option); + ui.vertical_centered(|ui| { + PopupWindow::create_close_button(ui, popup_window_option); + }); }); } @@ -286,32 +283,22 @@ impl<'a> PopupWindow<'a> { popup_window_option: &mut Option, error: GuiError, ) { - egui::Window::new(error.title) - .resizable(false) - .collapsible(false) - .default_width(popup_metadata.width) - .default_pos((popup_metadata.default_x_pos, popup_metadata.default_y_pos)) + PopupWindow::create_window(error.title.as_str(), popup_metadata.width, popup_metadata.default_pos) .show(popup_metadata.ctx, |ui| { - ui.vertical(|ui| { + ui.vertical_centered(|ui| { // error message - ui.horizontal(|ui| { - if error.err_lvl == GuiErrorLevel::Critical { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label(&*error.desc); - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("Please report this bug to the "); - ui.hyperlink_to("Github Issues Page", "https://github.com/Crypto-Spartan/unifi-search-tool/issues"); - ui.label(" and include as much information as possible.") - }); - }); - } else { - ui.with_layout(egui::Layout::centered_and_justified(egui::Direction::TopDown), |ui| { - ui.label(error.desc.as_ref()); - }); - } - }); + if error.err_lvl == GuiErrorLevel::Critical { + ui.label(&*error.desc); + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("Please report this bug to the "); + ui.hyperlink_to("Github Issues Page", "https://github.com/Crypto-Spartan/unifi-search-tool/issues"); + ui.label(" and include as much information as possible.") + }); + } else { + ui.label(error.desc.as_ref()); + } // close button PopupWindow::create_close_button(ui, popup_window_option); @@ -324,26 +311,10 @@ impl<'a> PopupWindow<'a> { popup_window_option: &mut Option, device_rx: &mut flume::Receiver, ) { - let WindowMeta { - ctx, - width, - default_x_pos, - default_y_pos, - } = popup_metadata; - - egui::Window::new("Cancel") - .resizable(false) - .collapsible(false) - .default_width(width) - .default_pos((default_x_pos, default_y_pos)) - .show(ctx, |ui| { - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::centered_and_justified(egui::Direction::TopDown), - |ui| { - ui.label("Cancel in progress, please wait..."); - }, - ); + PopupWindow::create_window("Cancel", popup_metadata.width, popup_metadata.default_pos) + .show(popup_metadata.ctx, |ui| { + ui.vertical_centered(|ui| { + ui.label("Cancel in progress, please wait..."); }); }); @@ -354,15 +325,8 @@ impl<'a> PopupWindow<'a> { #[inline] fn create_close_button(ui: &mut egui::Ui, popup_window_option: &mut Option) { - ui.horizontal(|ui| { - ui.with_layout( - egui::Layout::centered_and_justified(egui::Direction::BottomUp), - |ui| { - if ui.button("Close").clicked() { - *popup_window_option = None; - } - }, - ); - }); + if ui.button("Close").clicked() { + *popup_window_option = None; + } } } diff --git a/src/mac_address/mod.rs b/src/mac_address/mod.rs index f264cfc..4e01a5e 100644 --- a/src/mac_address/mod.rs +++ b/src/mac_address/mod.rs @@ -5,7 +5,7 @@ use thiserror::Error; pub mod validation; use validation::MAC_ADDR_REGEX_STR; -#[derive(Clone, Debug, Default)]//, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq)] pub(crate) struct MacAddress{ bytes: [u8; 6] } diff --git a/src/unifi/devices.rs b/src/unifi/devices.rs index 3520473..f9335af 100644 --- a/src/unifi/devices.rs +++ b/src/unifi/devices.rs @@ -45,7 +45,7 @@ impl DeviceState { } } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] pub(crate) struct UnifiDeviceBasic { pub(crate) mac: MacAddress, pub(crate) state: DeviceState,