From ea29c04a32c0756e3d5bb8c9886d16a88decec84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabor=20K=C3=B6rber?= Date: Thu, 6 Jun 2024 15:59:02 +0200 Subject: [PATCH] tray icon example func --- src/bin/ui_tray_icon.rs | 246 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 src/bin/ui_tray_icon.rs diff --git a/src/bin/ui_tray_icon.rs b/src/bin/ui_tray_icon.rs new file mode 100644 index 0000000..0083c18 --- /dev/null +++ b/src/bin/ui_tray_icon.rs @@ -0,0 +1,246 @@ +use axum::{ + routing::{self, get}, + Router, +}; +use eframe::{egui, epi}; +use serde::{Deserialize, Serialize}; +use std::{ + net::SocketAddr, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, Mutex, + }, + thread, +}; +use tokio::sync::mpsc; +use tokio::task::JoinHandle; +use tray_item::TrayItem; + +#[derive(Debug, Clone)] +struct AppState { + settings: Arc>, + should_run: Arc>, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ServerSettings { + port: String, // Change to String for text editing +} + +async fn home() -> &'static str { + "Hello, World!" +} + +#[tokio::main] +async fn main() { + let (tx, rx) = mpsc::channel(32); + let settings = Arc::new(Mutex::new(ServerSettings { + port: "3311".to_string(), + })); + let should_run = Arc::new(Mutex::new(true)); + let app_state = AppState { + settings: settings.clone(), + should_run: should_run.clone(), + }; + + // Run the server in a separate thread + let server_thread = thread::spawn(move || { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(async { + run_server(app_state, rx).await; + }); + }); + + // Atomic bool to track UI visibility + let ui_visible = Arc::new(AtomicBool::new(true)); + + // Set up the system tray icon + let (tray_tx, tray_rx) = std::sync::mpsc::channel(); + let mut tray = TrayItem::new("App Name", "icon-red").unwrap(); + tray.add_label("Server Control").unwrap(); + tray.add_menu_item("Show/Hide", { + let tray_tx = tray_tx.clone(); + move || { + tray_tx.send(()).unwrap(); + } + }) + .unwrap(); + tray.add_menu_item("Quit", move || { + std::process::exit(0); + }) + .unwrap(); + + // Thread to handle tray icon interactions + let ui_visible_clone = ui_visible.clone(); + thread::spawn(move || { + for _ in tray_rx { + let visible = ui_visible_clone.load(Ordering::SeqCst); + println!("visup"); + ui_visible_clone.store(!visible, Ordering::SeqCst); + } + }); + + // Run the UI on the main thread + run_ui(settings, should_run, tx, ui_visible); + + // Wait for the server thread to finish + server_thread.join().unwrap(); +} + +async fn run_server(app_state: AppState, mut rx: mpsc::Receiver) { + let mut server_handle = start_server(&app_state); + + loop { + tokio::select! { + _ = &mut server_handle => { + // Server has stopped + break; + }, + Some(message) = rx.recv() => { + match message { + ServerMessage::UpdateSettings(new_settings) => { + println!("Received new settings: {:?}", new_settings); + let mut settings_guard = app_state.settings.lock().unwrap(); + *settings_guard = new_settings; + + // Restart the server with new settings + println!("Aborting current server..."); + server_handle.abort(); // Cancel the previous server task + println!("Starting new server..."); + drop(settings_guard); // Ensure the lock is released before starting the new server + server_handle = start_server(&app_state); + println!("Server started."); + }, + ServerMessage::Interact => { + // Example interaction: print current settings + let settings_guard = app_state.settings.lock().unwrap(); + println!("Current settings: {:?}", *settings_guard); + }, + ServerMessage::Shutdown => { + // Handle server shutdown + println!("Shutting down server..."); + let mut should_run_guard = app_state.should_run.lock().unwrap(); + *should_run_guard = false; + server_handle.abort(); + println!("Server shutdown."); + break; + }, + // Handle other message types here + } + } + } + } +} + +fn start_server(app_state: &AppState) -> JoinHandle<()> { + // Compute the address before entering the async block + let addr = { + let settings = app_state.settings.lock().unwrap(); + let port: u16 = settings.port.parse().unwrap_or(3311); + SocketAddr::from(([0, 0, 0, 0], port)) + }; + + let server_app_state = app_state.clone(); + + println!("listening on {}", addr); + + tokio::spawn(async move { + let app = Router::new() + .route("/", get(home)) + .with_state(server_app_state.clone()); + let server = axum::Server::bind(&addr).serve(app.into_make_service()); + + // Run the server and monitor the should_run flag + tokio::select! { + _ = server => {}, + _ = monitor_shutdown(server_app_state.should_run.clone()) => { + println!("Server shutdown signal received"); + } + } + }) +} + +async fn monitor_shutdown(should_run: Arc>) { + while *should_run.lock().unwrap() { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } +} + +fn run_ui( + settings: Arc>, + should_run: Arc>, + tx: mpsc::Sender, + ui_visible: Arc, +) { + let app = NativeApp { + settings, + should_run, + tx, + ui_visible, + }; + let native_options = eframe::NativeOptions::default(); + eframe::run_native(Box::new(app), native_options); +} + +struct NativeApp { + settings: Arc>, + should_run: Arc>, + tx: mpsc::Sender, + ui_visible: Arc, +} + +impl epi::App for NativeApp { + fn update(&mut self, ctx: &egui::CtxRef, frame: &eframe::epi::Frame) { + // Check visibility state and hide if needed + if !self.ui_visible.load(Ordering::SeqCst) { + eprintln!("frame switch!"); + // Do not display the central panel if the UI should be hidden + //self.ui_visible.store(true, Ordering::SeqCst); // Reset visibility state for next show + } else { + egui::CentralPanel::default().show(ctx, |ui| { + let mut settings = self.settings.lock().unwrap(); + + ui.heading("Server Settings"); + + ui.horizontal(|ui| { + ui.label("Port:"); + if ui.text_edit_singleline(&mut settings.port).changed() {} + }); + + if ui.button("Apply").clicked() { + if self + .tx + .try_send(ServerMessage::UpdateSettings(settings.clone())) + .is_err() + { + eprintln!("Failed to send settings update"); + } + } + + if ui.button("Interact").clicked() { + if self.tx.try_send(ServerMessage::Interact).is_err() { + eprintln!("Failed to send interaction message"); + } + } + + if ui.button("Shutdown Server").clicked() { + if self.tx.try_send(ServerMessage::Shutdown).is_err() { + eprintln!("Failed to send shutdown message"); + } + } + }); + } + } + + fn name(&self) -> &str { + "NativeApp" + } +} + +#[derive(Debug, Clone)] +enum ServerMessage { + UpdateSettings(ServerSettings), + Interact, + Shutdown, + // Add other message types here +}