use gtk::{glib, prelude::*, Box, Entry, Label, ListBox}; use gtk::{Application, ApplicationWindow, Button}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, atomic::AtomicBool}; use log::error; use crate::config::load_config; use crate::{joystick_loop, JoystickThreadUpdate}; pub struct SocketConnectionUpdate { pub ip: String, pub port: u32, pub start_websocket: bool, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct AppState { pub ip: String, pub port: u32, } impl Default for AppState { fn default() -> Self { AppState { ip: "10.0.0.29".to_string(), port: 8765, } } } pub fn build_ui(app: &Application) { let initial_settings = load_config(); let main_box = ListBox::new(); let do_run: Arc = Arc::new(AtomicBool::new(true)); let do_run2 = do_run.clone(); let (tx, rx) = async_channel::bounded::(4); let (tx2, rx2) = async_channel::bounded::(1); let _ = std::thread::spawn(move || joystick_loop::joystick_websocket_loop(tx, do_run2, rx2)); // let conn_status_label = Label::new(Some(&"No Connection".to_string())); let conn_status_label = Label::builder() .label("No Connection".to_string()) .can_focus(true) .build(); let content_box = Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(10) .margin_top(12) .margin_start(24) .margin_end(24) .margin_bottom(12) .build(); let ip_entry = Entry::builder() .placeholder_text("IP Address") .text(initial_settings.ip) .can_focus(true) .build(); let port_entry = Entry::builder() .placeholder_text("Port") .text(initial_settings.port.to_string()) .can_focus(true) .build(); let button = Button::builder().margin_top(12).build(); content_box.append(&ip_entry); content_box.append(&port_entry); content_box.append(&button); let axis_label = Label::builder() .label("X: 0 Y: )") .justify(gtk::Justification::Center) .css_classes(vec!["JoystickCurrent"]) .build(); main_box.append(&conn_status_label); main_box.append(&content_box); main_box.append(&axis_label); // Connect to "clicked" signal of `button` button.connect_clicked(glib::clone!(@weak ip_entry, @weak port_entry, @strong tx2 => move |_button| { // Set the label to "Hello World!" after the button has been clicked on let ip_text = ip_entry.text(); let port_text = port_entry.text(); if ip_text.len() > 0 { if let Ok(val) = port_text.parse::() { match tx2.try_send(SocketConnectionUpdate { ip: ip_text.to_string(), port: val, start_websocket: ip_entry.get_sensitive(), }) { Ok(_) => { } Err(async_channel::TrySendError::Closed(_)) => {panic!("Joystick thread was closed. Unrecoverable")} Err(e) => {error!("There was an error: {e}")} } } } })); glib::spawn_future_local( glib::clone!(@weak axis_label, @weak button, @weak conn_status_label, @weak ip_entry, @weak port_entry, @strong rx => async move { while let Ok(msg) = rx.recv().await { axis_label.set_text( format!("X: {:>4} Y: {:>4}", msg.x_axis.unwrap_or("0".to_string()), msg.y_axis.unwrap_or("0".to_string())).as_str() ); button.set_label({ if msg.connected { ip_entry.set_sensitive(false); port_entry.set_sensitive(false); "Currently Connected" } else { ip_entry.set_sensitive(true); port_entry.set_sensitive(true); "Currently Disconnected" } }); if msg.connected { conn_status_label.set_css_classes(&["YesConnection"]); button.set_css_classes(&["YesConnection"]); } else { conn_status_label.set_css_classes(&["NoConnection"]); button.set_css_classes(&["NoConnection"]); } } }), ); // Create a window let window = ApplicationWindow::builder() .application(app) .title("VCC Camera Controller") .child(&main_box) .build(); window.connect_close_request(move |_| { do_run.store(false, std::sync::atomic::Ordering::SeqCst); glib::Propagation::Proceed }); // Present window window.present(); }