working manual change of tracking id

This commit is contained in:
Nickiel12 2024-05-04 15:09:31 -07:00
parent 22a81ee44e
commit b5c7afadcb
10 changed files with 284 additions and 153 deletions

View file

@ -11,16 +11,15 @@ use futures_util::{
stream::{SplitSink, SplitStream}, stream::{SplitSink, SplitStream},
SinkExt, StreamExt, SinkExt, StreamExt,
}; };
use gstreamer::State;
use gstreamer::prelude::ElementExt; use gstreamer::prelude::ElementExt;
use log::{error, info}; use gstreamer::State;
use log::{error, info, debug};
use tokio::net::TcpStream; use tokio::net::TcpStream;
use tokio::runtime::Handle; use tokio::runtime::Handle;
use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream}; use tokio_tungstenite::{connect_async, tungstenite::Message, MaybeTlsStream, WebSocketStream};
use crate::remote_sources::TrackerState; use crate::remote_sources::TrackerState;
use crate::{gstreamer_pipeline, remote_sources}; use crate::{gstreamer_pipeline, remote_sources};
use crate::ui::NormalizedBoxCoords;
use crate::{joystick_source::joystick_loop, ui::GuiUpdate}; use crate::{joystick_source::joystick_loop, ui::GuiUpdate};
const PRIORITY_TIMEOUT: Duration = Duration::from_secs(2); const PRIORITY_TIMEOUT: Duration = Duration::from_secs(2);
@ -48,7 +47,6 @@ pub enum ApplicationEvent {
struct CoordState<'a> { struct CoordState<'a> {
pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>, pub sck_outbound: Option<SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>,
pub sck_alive_server: Arc<AtomicBool>, pub sck_alive_server: Arc<AtomicBool>,
pub identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>,
pub sck_alive_recvr: Arc<AtomicBool>, pub sck_alive_recvr: Arc<AtomicBool>,
pub joystick_loop_alive: Arc<AtomicBool>, pub joystick_loop_alive: Arc<AtomicBool>,
@ -72,14 +70,12 @@ impl<'a> CoordState<'a> {
to_mec: Sender<ApplicationEvent>, to_mec: Sender<ApplicationEvent>,
to_gui: Sender<GuiUpdate>, to_gui: Sender<GuiUpdate>,
rt: Handle, rt: Handle,
identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>,
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
) -> Self { ) -> Self {
let this = CoordState { let this = CoordState {
sck_outbound: None, sck_outbound: None,
sck_alive_recvr: Arc::new(AtomicBool::new(false)), sck_alive_recvr: Arc::new(AtomicBool::new(false)),
sck_alive_server: Arc::new(AtomicBool::new(false)), sck_alive_server: Arc::new(AtomicBool::new(false)),
identity_boxes,
joystick_loop_alive: Arc::new(AtomicBool::new(false)), joystick_loop_alive: Arc::new(AtomicBool::new(false)),
current_priority: ConnectionType::Local, current_priority: ConnectionType::Local,
@ -95,13 +91,14 @@ impl<'a> CoordState<'a> {
tracker_state, tracker_state,
}; };
this.rt.spawn(crate::remote_sources::shared_video_pipe::create_outbound_pipe( this.rt.spawn(
crate::remote_sources::shared_video_pipe::create_outbound_pipe(
this.pipeline.sink_frame.clone(), this.pipeline.sink_frame.clone(),
this.to_mec.clone(), this.to_mec.clone(),
this.keep_windows_pipe_alive.clone(), this.keep_windows_pipe_alive.clone(),
this.identity_boxes.clone(),
this.tracker_state.clone(), this.tracker_state.clone(),
)); ),
);
this this
} }
@ -199,21 +196,27 @@ pub async fn start_coordinator(
to_mec: Sender<ApplicationEvent>, to_mec: Sender<ApplicationEvent>,
to_gui: Sender<GuiUpdate>, to_gui: Sender<GuiUpdate>,
runtime: Handle, runtime: Handle,
identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>,
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
) { ) {
info!("Starting coordinator!"); info!("Starting coordinator!");
let mec = pin!(mec); let mec = pin!(mec);
let mut state = CoordState::new(mec, to_mec, to_gui, runtime, identity_boxes, tracker_state); let mut state = CoordState::new(mec, to_mec, to_gui, runtime, tracker_state);
state.pipeline state
.pipeline
.pipeline .pipeline
.set_state(State::Playing) .set_state(State::Playing)
.expect("Could not set pipeline state to playing"); .expect("Could not set pipeline state to playing");
if let Err(e) = state.to_gui.send(GuiUpdate::UpdatePaintable(state.pipeline.sink_paintable.clone())).await { if let Err(e) = state
.to_gui
.send(GuiUpdate::UpdatePaintable(
state.pipeline.sink_paintable.clone(),
))
.await
{
error!("Could not send new paintable to GUI: {e}"); error!("Could not send new paintable to GUI: {e}");
} }
@ -236,10 +239,11 @@ pub async fn start_coordinator(
state.socket_send(socket_message).await; state.socket_send(socket_message).await;
} }
ApplicationEvent::EnableAutomatic(do_enable) => { ApplicationEvent::EnableAutomatic(do_enable) => {
debug!("Trying to get lock on tracker_state for enable automatic");
if let Ok(mut ts) = state.tracker_state.lock() { if let Ok(mut ts) = state.tracker_state.lock() {
ts.enabled = do_enable; ts.enabled = do_enable;
} }
}, }
ApplicationEvent::MoveEvent(coord, priority) => { ApplicationEvent::MoveEvent(coord, priority) => {
// If Automatic control, but local event happens, override the automatice events for 2 seconds // If Automatic control, but local event happens, override the automatice events for 2 seconds
if priority <= state.current_priority if priority <= state.current_priority
@ -268,7 +272,8 @@ pub async fn start_coordinator(
} }
} }
state.pipeline state
.pipeline
.pipeline .pipeline
.set_state(State::Null) .set_state(State::Null)
.expect("Could not set pipeline state to playing"); .expect("Could not set pipeline state to playing");

View file

@ -51,9 +51,11 @@ impl WebcamPipeline {
.build() .build()
.expect("Could not build videoscale for GStreamer"); .expect("Could not build videoscale for GStreamer");
let caps_string = String::from("video/x-raw,format=RGB,width=640,height=480,max-buffers=1,drop=true"); let caps_string =
String::from("video/x-raw,format=RGB,width=640,height=480,max-buffers=1,drop=true");
// let caps_string = String::from("video/x-raw,format=RGB,max-buffers=1,drop=true"); // let caps_string = String::from("video/x-raw,format=RGB,max-buffers=1,drop=true");
let appsrc_caps = gstreamer::Caps::from_str(&caps_string).expect("Couldn't create appsrc caps"); let appsrc_caps =
gstreamer::Caps::from_str(&caps_string).expect("Couldn't create appsrc caps");
/* /*
// let sink_frame = ElementFactory::make("appsink") // let sink_frame = ElementFactory::make("appsink")
@ -76,7 +78,8 @@ impl WebcamPipeline {
sink_frame.set_property("caps", &appsrc_caps.to_value()); sink_frame.set_property("caps", &appsrc_caps.to_value());
pipeline.add_many(&[ pipeline
.add_many(&[
&source, &source,
&convert, &convert,
&tee, &tee,
@ -85,7 +88,8 @@ impl WebcamPipeline {
&resize, &resize,
&queue, &queue,
&sink_frame.upcast_ref(), &sink_frame.upcast_ref(),
]).expect("Could not link the elements to the pipeline"); ])
.expect("Could not link the elements to the pipeline");
source source
.link(&convert) .link(&convert)
@ -115,14 +119,11 @@ impl WebcamPipeline {
.link(&sink_frameoutput_sinkpad) .link(&sink_frameoutput_sinkpad)
.expect("Could not link tee srcpad 2 to frame output sink pad"); .expect("Could not link tee srcpad 2 to frame output sink pad");
queue queue.link(&resize).expect("Could not link queue to resize");
.link(&resize)
.expect("Could not link queue to resize");
resize resize
.link(&sink_frame) .link(&sink_frame)
.expect("Could not bind resize to appsrc"); .expect("Could not bind resize to appsrc");
WebcamPipeline { WebcamPipeline {
pipeline, pipeline,
src: source, src: source,

View file

@ -8,9 +8,9 @@ use tokio::runtime;
mod config; mod config;
mod coordinator; mod coordinator;
mod gstreamer_pipeline;
mod joystick_source; mod joystick_source;
mod remote_sources; mod remote_sources;
mod gstreamer_pipeline;
mod ui; mod ui;
const APP_ID: &str = "net.nickiel.joystick-controller-client"; const APP_ID: &str = "net.nickiel.joystick-controller-client";

View file

@ -24,12 +24,16 @@ mod process_box_string;
mod remote_source; mod remote_source;
pub mod shared_video_pipe; pub mod shared_video_pipe;
use crate::coordinator::{ApplicationEvent, ConnectionType}; use crate::{coordinator::{ApplicationEvent, ConnectionType}, ui::NormalizedBoxCoords};
pub struct TrackerState { pub struct TrackerState {
pub tracking_id: u32, pub tracking_id: u32,
pub last_detect: Instant, pub last_detect: Instant,
pub enabled: bool, pub enabled: bool,
pub update_ids: bool,
pub identity_boxes: Vec<NormalizedBoxCoords>,
} }
pub async fn start_socketserver( pub async fn start_socketserver(
@ -42,7 +46,6 @@ pub async fn start_socketserver(
let listener = TcpListener::bind(&addr).await.expect("Can't listen"); let listener = TcpListener::bind(&addr).await.expect("Can't listen");
info!("Listening on: {}", addr); info!("Listening on: {}", addr);
while let Ok((stream, _)) = listener.accept().await { while let Ok((stream, _)) = listener.accept().await {
let peer = stream let peer = stream
.peer_addr() .peer_addr()
@ -66,9 +69,7 @@ async fn accept_connection(
mec: Sender<ApplicationEvent>, mec: Sender<ApplicationEvent>,
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
) { ) {
if let Err(e) = if let Err(e) = handle_connection(peer, stream, mec.clone(), tracker_state).await {
handle_connection(peer, stream, mec.clone(), tracker_state).await
{
match e { match e {
Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => (), Error::ConnectionClosed | Error::Protocol(_) | Error::Utf8 => (),
err => error!("Error processing connection: {}", err), err => error!("Error processing connection: {}", err),

View file

@ -1,12 +1,11 @@
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::ui::NormalizedBoxCoords; use crate::ui::NormalizedBoxCoords;
use super::TrackerState;
pub fn process_incoming_string( pub fn process_incoming_string(
message: String, message: String,
identity_boxes: &Arc<Mutex<Vec<NormalizedBoxCoords>>>, // This goes all the way back to the GUI thread for drawing boxes identity_boxes: &Arc<Mutex<TrackerState>>, // This goes all the way back to the GUI thread for drawing boxes
) -> core::result::Result<(), String> { ) -> core::result::Result<(), String> {
let mut boxes: Vec<NormalizedBoxCoords> = Vec::new(); let mut boxes: Vec<NormalizedBoxCoords> = Vec::new();
@ -37,22 +36,26 @@ pub fn process_incoming_string(
let x2: u32 = coords2[0].parse().map_err(|_| "Invalid width")?; let x2: u32 = coords2[0].parse().map_err(|_| "Invalid width")?;
let y2: u32 = coords2[1].parse().map_err(|_| "Invalid width")?; let y2: u32 = coords2[1].parse().map_err(|_| "Invalid width")?;
boxes.push( boxes.push(NormalizedBoxCoords {
NormalizedBoxCoords
{
id, id,
x1: (x1 as f32 / 1000.0), x1: (x1 as f32 / 1000.0),
x2: (x2 as f32 / 1000.0), x2: (x2 as f32 / 1000.0),
y1: (y1 as f32 / 1000.0), y1: (y1 as f32 / 1000.0),
y2: (y2 as f32 / 1000.0), y2: (y2 as f32 / 1000.0),
} });
);
} }
// Replace the memory address in the mutex guard with that of the created vec above // Replace the memory address in the mutex guard with that of the created vec above
if let Ok(mut ib) = identity_boxes.lock() { if let Ok(mut ib) = identity_boxes.lock() {
let mut old_ids: Vec<u32> = ib.identity_boxes.iter().map(|x| x.id).collect();
old_ids.sort();
let mut new_ids: Vec<u32> = boxes.iter().map(|x| x.id).collect();
new_ids.sort();
ib.update_ids = new_ids == old_ids;
// Replace the memory address in the mutex guard with that of the created vec above // Replace the memory address in the mutex guard with that of the created vec above
*ib = boxes; ib.identity_boxes = boxes;
} }
Ok(()) Ok(())

View file

@ -1,5 +1,7 @@
use std::{ use std::{
cmp::{max, min}, sync::{atomic::AtomicBool, Arc, Mutex}, time::{Duration, Instant} cmp::{max, min},
sync::{atomic::AtomicBool, Arc, Mutex},
time::{Duration, Instant},
}; };
use async_channel::Sender; use async_channel::Sender;
@ -9,17 +11,19 @@ use interprocess::os::windows::named_pipe::{
pipe_mode::{self, Bytes}, pipe_mode::{self, Bytes},
tokio::{DuplexPipeStream, PipeStream}, tokio::{DuplexPipeStream, PipeStream},
}; };
use log::{error, info}; use log::{error, info, debug};
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::{coordinator::{ApplicationEvent, MoveEvent}, remote_sources::process_box_string, ui::NormalizedBoxCoords}; use crate::{
coordinator::{ApplicationEvent, MoveEvent},
remote_sources::process_box_string,
};
use super::TrackerState; use super::TrackerState;
struct LoopState { struct LoopState {
pub appsink: Arc<Mutex<AppSink>>, pub appsink: Arc<Mutex<AppSink>>,
pub mec: Sender<ApplicationEvent>, pub mec: Sender<ApplicationEvent>,
pub identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>, // This goes all the way back to the GUI thread for drawing boxes
pub tracker_state: Arc<Mutex<TrackerState>>, pub tracker_state: Arc<Mutex<TrackerState>>,
pub pipe: PipeStream<Bytes, Bytes>, pub pipe: PipeStream<Bytes, Bytes>,
@ -31,48 +35,59 @@ struct LoopState {
impl LoopState { impl LoopState {
fn get_video_frame(&mut self) -> Result<VideoFrame<Readable>, String> { fn get_video_frame(&mut self) -> Result<VideoFrame<Readable>, String> {
let sample = self.appsink.lock().map_err(|e| format!("Could not get a lock on the appsink: {e}"))?.pull_sample().map_err(|e| format!("Could not pull appsink sample: {e}"))?; let sample = self
.appsink
.lock()
.map_err(|e| format!("Could not get a lock on the appsink: {e}"))?
.pull_sample()
.map_err(|e| format!("Could not pull appsink sample: {e}"))?;
let buffer = sample.buffer_owned().unwrap(); let buffer = sample.buffer_owned().unwrap();
gstreamer_video::VideoFrame::from_buffer_readable(buffer, &self.video_info).map_err(|_| format!("Unable to make video frame from buffer!")) gstreamer_video::VideoFrame::from_buffer_readable(buffer, &self.video_info)
.map_err(|_| format!("Unable to make video frame from buffer!"))
} }
async fn read_return_message(&mut self) -> Result<String, String> { async fn read_return_message(&mut self) -> Result<String, String> {
// Read message size from the pipe // Read message size from the pipe
if let Err(e) = self.pipe.read_exact(&mut self.len_buf).await { if let Err(e) = self.pipe.read_exact(&mut self.len_buf).await {
return Err(format!("Couldn't read message length from the windows pipe: {e}")); return Err(format!(
"Couldn't read message length from the windows pipe: {e}"
));
} }
let length = u32::from_le_bytes(self.len_buf); let length = u32::from_le_bytes(self.len_buf);
self.byte_buffer.resize(length as usize, 0); self.byte_buffer.resize(length as usize, 0);
// Read the message of message length from the pipe // Read the message of message length from the pipe
if let Err(e) = self.pipe.read_exact(&mut self.byte_buffer).await { if let Err(e) = self.pipe.read_exact(&mut self.byte_buffer).await {
return Err(format!("Couldn't read the message from the windows pipe: {e}")); return Err(format!(
"Couldn't read the message from the windows pipe: {e}"
));
} }
Ok(String::from_utf8_lossy(&self.byte_buffer).to_string()) Ok(String::from_utf8_lossy(&self.byte_buffer).to_string())
} }
} }
pub async fn create_outbound_pipe( pub async fn create_outbound_pipe(
appsink: Arc<Mutex<AppSink>>, appsink: Arc<Mutex<AppSink>>,
mec: Sender<ApplicationEvent>, mec: Sender<ApplicationEvent>,
keep_alive: Arc<AtomicBool>, keep_alive: Arc<AtomicBool>,
identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>, // This goes all the way back to the GUI thread for drawing boxes
tracker_state: Arc<Mutex<TrackerState>>, tracker_state: Arc<Mutex<TrackerState>>,
) { ) {
if let Ok(pipe) = if let Ok(pipe) =
DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(r"\\.\pipe\example_pipe").await DuplexPipeStream::<pipe_mode::Bytes>::connect_by_path(r"\\.\pipe\example_pipe").await
{ {
let mut state = LoopState { let mut state = LoopState {
appsink, appsink,
mec, mec,
identity_boxes,
tracker_state, tracker_state,
pipe, pipe,
video_info: gstreamer_video::VideoInfo::builder(gstreamer_video::VideoFormat::Rgb, 640, 480) .build() .expect("Couldn't build video info!"), video_info: gstreamer_video::VideoInfo::builder(
gstreamer_video::VideoFormat::Rgb,
640,
480,
)
.build()
.expect("Couldn't build video info!"),
byte_buffer: Vec::new(), byte_buffer: Vec::new(),
len_buf: [0; 4], len_buf: [0; 4],
}; };
@ -100,9 +115,9 @@ pub async fn create_outbound_pipe(
}; };
// info!("Video frame {}x{} with stride of {}, is this many bytes: {}", video_frame.width(), video_frame.height(), video_frame.plane_stride()[0], video_frame.plane_data(0).unwrap().len()); // info!("Video frame {}x{} with stride of {}, is this many bytes: {}", video_frame.width(), video_frame.height(), video_frame.plane_stride()[0], video_frame.plane_data(0).unwrap().len());
// Send video frame to pipe // Send video frame to pipe
if let Err(e) = send_to_pipe(&mut state.pipe, video_frame.plane_data(0).unwrap()).await { if let Err(e) = send_to_pipe(&mut state.pipe, video_frame.plane_data(0).unwrap()).await
{
error!("Error in sending to the pipe: {e}"); error!("Error in sending to the pipe: {e}");
break; break;
} }
@ -119,13 +134,15 @@ pub async fn create_outbound_pipe(
let y_off: i32; let y_off: i32;
// Load the tracking boxes into identity_boxes, then do the adjustment calcuations on the updated tracking info (side-effects) // Load the tracking boxes into identity_boxes, then do the adjustment calcuations on the updated tracking info (side-effects)
(x_off, y_off) = process_box_string::process_incoming_string(message.to_string(), &state.identity_boxes) (x_off, y_off) = process_box_string::process_incoming_string(
.and_then(|_| calculate_tracking(&state.tracker_state, &state.identity_boxes)) message.to_string(),
&state.tracker_state,
)
.and_then(|_| calculate_tracking(&state.tracker_state))
.unwrap_or((0, 0)); .unwrap_or((0, 0));
if let Err(e) = state
.mec
if let Err(e) = state.mec
.send(ApplicationEvent::MoveEvent( .send(ApplicationEvent::MoveEvent(
MoveEvent { x: x_off, y: y_off }, MoveEvent { x: x_off, y: y_off },
crate::coordinator::ConnectionType::Automated, crate::coordinator::ConnectionType::Automated,
@ -148,7 +165,7 @@ pub async fn create_outbound_pipe(
async fn send_to_pipe<'a>( async fn send_to_pipe<'a>(
pipe: &mut PipeStream<pipe_mode::Bytes, pipe_mode::Bytes>, pipe: &mut PipeStream<pipe_mode::Bytes, pipe_mode::Bytes>,
message: &'a [u8] message: &'a [u8],
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<(), Box<dyn std::error::Error>> {
pipe.write_all(message).await?; pipe.write_all(message).await?;
// pipe.shutdown().await?; // pipe.shutdown().await?;
@ -156,20 +173,17 @@ async fn send_to_pipe<'a>(
Ok(()) Ok(())
} }
fn calculate_tracking( fn calculate_tracking(
tracker_state: &Arc<Mutex<TrackerState>>, tracker_state: &Arc<Mutex<TrackerState>>,
identity_boxes: &Arc<Mutex<Vec<NormalizedBoxCoords>>>, // This goes all the way back to the GUI thread for drawing boxes
) -> core::result::Result<(i32, i32), String> { ) -> core::result::Result<(i32, i32), String> {
debug!("Getting lock on tracker state for caculate tracking");
if let Ok(boxes) = identity_boxes.lock() {
if let Ok(mut ts) = tracker_state.lock() { if let Ok(mut ts) = tracker_state.lock() {
if ts.last_detect + Duration::from_secs(2) < Instant::now() && !boxes.is_empty() { if ts.last_detect + Duration::from_secs(2) < Instant::now() && !ts.identity_boxes.is_empty() {
info!("Setting new target: {}", boxes[0].id); info!("Setting new target: {}", ts.identity_boxes[0].id);
ts.tracking_id = boxes[0].id; ts.tracking_id = ts.identity_boxes[0].id;
} }
if let Some(target_box) = boxes.iter().find(|e| e.id == ts.tracking_id) { if let Some(target_box) = ts.identity_boxes.iter().find(|e| e.id == ts.tracking_id) {
let x_adjust = calc_x_adjust(target_box.x1, target_box.x2); let x_adjust = calc_x_adjust(target_box.x1, target_box.x2);
let y_adjust = calc_y_adjust(target_box.y1); let y_adjust = calc_y_adjust(target_box.y1);
ts.last_detect = Instant::now(); ts.last_detect = Instant::now();
@ -180,9 +194,6 @@ fn calculate_tracking(
} else { } else {
Err("Couldn't lock tracker state".to_string()) Err("Couldn't lock tracker state".to_string())
} }
} else {
Err("Couldn't lock identity boxes".to_string())
}
} }
fn calc_x_adjust(x1: f32, x2: f32) -> i32 { fn calc_x_adjust(x1: f32, x2: f32) -> i32 {

View file

@ -5,7 +5,7 @@ use gtk::cairo::Context;
use gtk::gdk::Paintable; use gtk::gdk::Paintable;
use gtk::{glib, prelude::*, AspectFrame, Label, ListBox}; use gtk::{glib, prelude::*, AspectFrame, Label, ListBox};
use gtk::{Application, ApplicationWindow}; use gtk::{Application, ApplicationWindow};
use log::{info, error}; use log::{error, info, debug};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio::runtime::Handle; use tokio::runtime::Handle;
@ -85,20 +85,20 @@ pub fn build_ui(app: &Application, runtime: Handle) {
// Main Event Channel // Main Event Channel
let (to_mec, mec) = async_channel::unbounded::<ApplicationEvent>(); let (to_mec, mec) = async_channel::unbounded::<ApplicationEvent>();
let (to_gui, gui_recv) = async_channel::bounded::<GuiUpdate>(10); let (to_gui, gui_recv) = async_channel::bounded::<GuiUpdate>(10);
let identity_boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>> = Arc::new(Mutex::new(vec![]));
let tracker_state = Arc::new(Mutex::new(TrackerState { let tracker_state = Arc::new(Mutex::new(TrackerState {
tracking_id: 0, tracking_id: 0,
last_detect: Instant::now(), last_detect: Instant::now(),
enabled: true, enabled: true,
}));
identity_boxes: vec![],
update_ids: false,
}));
runtime.spawn(start_coordinator( runtime.spawn(start_coordinator(
mec, mec,
to_mec.clone(), to_mec.clone(),
to_gui, to_gui,
runtime.clone(), runtime.clone(),
identity_boxes.clone(),
tracker_state.clone(), tracker_state.clone(),
)); ));
@ -108,17 +108,21 @@ pub fn build_ui(app: &Application, runtime: Handle) {
.can_focus(true) .can_focus(true)
.build(); .build();
let tabpanel = gtk::Notebook::builder() let tabpanel = gtk::Notebook::builder().enable_popup(true).build();
.enable_popup(true)
.build();
let socket_panel = Arc::new(SocketPanel::new(&initial_settings)); let socket_panel = Arc::new(SocketPanel::new(&initial_settings));
socket_panel.connect_button_callback(to_mec.clone()); socket_panel.connect_button_callback(to_mec.clone());
tabpanel.append_page(socket_panel.get_top_level(), Some(&gtk::Label::new(Some("Cam Connection")))); tabpanel.append_page(
socket_panel.get_top_level(),
Some(&gtk::Label::new(Some("Cam Connection"))),
);
let tracker_panel = TrackerPanel::new(); let tracker_panel = TrackerPanel::new(tracker_state.clone());
tracker_panel.connect_button_callback(to_mec.clone()); tracker_panel.connect_button_callback(to_mec.clone());
tabpanel.append_page(tracker_panel.get_top_level(), Some(&Label::new(Some("Auto Settings")))); tabpanel.append_page(
tracker_panel.get_top_level(),
Some(&Label::new(Some("Auto Settings"))),
);
let axis_label = Label::builder() let axis_label = Label::builder()
.label("X: 0 Y: )") .label("X: 0 Y: )")
@ -126,19 +130,15 @@ pub fn build_ui(app: &Application, runtime: Handle) {
.css_classes(vec!["JoystickCurrent"]) .css_classes(vec!["JoystickCurrent"])
.build(); .build();
left_box.append(&conn_status_label); left_box.append(&conn_status_label);
left_box.append(&tabpanel); left_box.append(&tabpanel);
left_box.append(&axis_label); left_box.append(&axis_label);
main_box.append(&left_box); main_box.append(&left_box);
let webcam_picture = gtk::Picture::builder() let webcam_picture = gtk::Picture::builder().can_focus(false).build();
.can_focus(false)
.build();
let overlay_box = gtk::Overlay::builder() let overlay_box = gtk::Overlay::builder().build();
.build();
let aspect = AspectFrame::builder() let aspect = AspectFrame::builder()
.ratio(16.0 / 9.0) .ratio(16.0 / 9.0)
.obey_child(false) .obey_child(false)
@ -147,18 +147,46 @@ pub fn build_ui(app: &Application, runtime: Handle) {
main_box.append(&aspect); main_box.append(&aspect);
let drawable = gtk::DrawingArea::builder() let drawable = gtk::DrawingArea::builder()
// .content_height(480)
// .content_width(640)
.build(); .build();
let drawable_ts = tracker_state.clone();
drawable.set_draw_func(move |_, ctx, width, height| { drawable.set_draw_func(move |_, ctx, width, height| {
draw_boxes(width, height, &ctx, identity_boxes.clone(), tracker_state.clone()); draw_boxes(
width,
height,
&ctx,
&drawable_ts,
);
}); });
overlay_box.set_child(Some(&webcam_picture)); overlay_box.set_child(Some(&webcam_picture));
overlay_box.add_overlay(&drawable); overlay_box.add_overlay(&drawable);
let items = tracker_panel.items.clone();
glib::timeout_add_seconds_local(1,
glib::clone!(@strong items => move || {
debug!("Getting lock on tracker state for checking identity boxes");
// don't update the stringlist until after letting go of the tracker state
// due to async interweaving causing a mutex deadlock
let mut ids: Option<Vec<String>> = None;
if let Ok(ts) = tracker_state.lock() {
if ts.update_ids {
ids = Some(ts.identity_boxes
.iter()
.map(|t| t.id.to_string())
.collect());
}
}
if let Some (ids) = ids {
let old_len = items.n_items();
items.splice(0, old_len, &ids.iter().map(|x| x.as_str()).collect::<Vec<&str>>()[0..])
}
glib::ControlFlow::Continue
}));
glib::spawn_future_local( glib::spawn_future_local(
glib::clone!(@weak axis_label, @weak conn_status_label, @weak tabpanel, @strong socket_panel, @strong gui_recv, @weak drawable => async move { glib::clone!(@weak axis_label, @weak conn_status_label, @weak tabpanel, @strong socket_panel, @strong gui_recv, @weak drawable => async move {
@ -178,7 +206,7 @@ pub fn build_ui(app: &Application, runtime: Handle) {
"Currently Connected" "Currently Connected"
} else { } else {
socket_panel.set_sensitive(true); socket_panel.set_sensitive(true);
tabpanel.set_page(0); // tabpanel.set_page(0);
// tabpanel.set_show_tabs(false); // tabpanel.set_show_tabs(false);
"Currently Disconnected" "Currently Disconnected"
} }
@ -210,19 +238,24 @@ pub fn build_ui(app: &Application, runtime: Handle) {
window.present(); window.present();
} }
fn draw_boxes(width: i32, height: i32, ctx: &Context, boxes: Arc<Mutex<Vec<NormalizedBoxCoords>>>, tracker_state: Arc<Mutex<TrackerState>>) { fn draw_boxes(
width: i32,
height: i32,
ctx: &Context,
tracker_state: &Arc<Mutex<TrackerState>>,
) {
ctx.set_line_width(5.0); ctx.set_line_width(5.0);
ctx.select_font_face("Arial", gtk::cairo::FontSlant::Normal, gtk::cairo::FontWeight::Bold); ctx.select_font_face(
"Arial",
gtk::cairo::FontSlant::Normal,
gtk::cairo::FontWeight::Bold,
);
ctx.set_font_size(24.0); ctx.set_font_size(24.0);
let active: u32 = match tracker_state.lock() { debug!("Getting tracker state for drawing boxes");
Ok(e) => e.tracking_id, if let Ok(ts) = tracker_state.lock() {
Err(_) => 0 let active = ts.tracking_id;
}; for nb in ts.identity_boxes.iter() {
if let Ok(bxs) = boxes.lock() {
for nb in bxs.iter() {
if nb.id == active { if nb.id == active {
ctx.set_source_rgb(1.0, 0.0, 0.0); ctx.set_source_rgb(1.0, 0.0, 0.0);
} else { } else {
@ -244,7 +277,8 @@ fn draw_boxes(width: i32, height: i32, ctx: &Context, boxes: Arc<Mutex<Vec<Norma
ctx.move_to(b.x1 as f64 + 5.0, b.y1 as f64 + 18.0); ctx.move_to(b.x1 as f64 + 5.0, b.y1 as f64 + 18.0);
ctx.set_source_rgb(0.0, 0.0, 0.0); ctx.set_source_rgb(0.0, 0.0, 0.0);
ctx.show_text(&format!("[{}]", b.id)).expect("Couldn't show text!"); ctx.show_text(&format!("[{}]", b.id))
.expect("Couldn't show text!");
ctx.stroke() ctx.stroke()
.expect("GTK Cario context did not have a stroke!"); .expect("GTK Cario context did not have a stroke!");

View file

@ -1,5 +1,9 @@
use async_channel::Sender; use async_channel::Sender;
use gtk::{glib, prelude::{BoxExt, ButtonExt, EditableExt, WidgetExt}, Box, Button, Entry}; use gtk::{
glib,
prelude::{BoxExt, ButtonExt, EditableExt, WidgetExt},
Box, Button, Entry,
};
use log::error; use log::error;
use crate::{config::save_config, coordinator::ApplicationEvent}; use crate::{config::save_config, coordinator::ApplicationEvent};
@ -16,7 +20,6 @@ pub struct SocketPanel {
impl SocketPanel { impl SocketPanel {
pub fn new(initial_settings: &crate::ui::AppState) -> SocketPanel { pub fn new(initial_settings: &crate::ui::AppState) -> SocketPanel {
let content_box = Box::builder() let content_box = Box::builder()
.orientation(gtk::Orientation::Vertical) .orientation(gtk::Orientation::Vertical)
.spacing(10) .spacing(10)
@ -64,7 +67,6 @@ impl SocketPanel {
} }
pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) { pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) {
let ip_entry = &self.ip_entry; let ip_entry = &self.ip_entry;
let port_entry = &self.port_entry; let port_entry = &self.port_entry;

View file

@ -1,20 +1,73 @@
use std::sync::{Arc, Mutex};
use async_channel::Sender; use async_channel::Sender;
use gtk::{prelude::{BoxExt, ButtonExt, ToggleButtonExt}, Box, Label, ToggleButton}; use gtk::{
use log::error; glib::object::CastNone, prelude::{
BoxExt, ButtonExt, Cast, GObjectPropertyExpressionExt, ListItemExt, ListModelExt, ToggleButtonExt
use crate::coordinator::ApplicationEvent; }, Box, Label, ListItem, ListView, ScrolledWindow, SignalListItemFactory, SingleSelection, StringList, StringObject, ToggleButton, Widget
};
use log::{debug, error};
use crate::{coordinator::ApplicationEvent, remote_sources::TrackerState};
pub struct TrackerPanel { pub struct TrackerPanel {
top_level: Box, top_level: Box,
enable_disable: ToggleButton, enable_disable: ToggleButton,
current_id: Label, current_id: Label,
scrolled_window: ScrolledWindow,
pub items: StringList,
list_view: ListView,
} }
impl TrackerPanel { impl TrackerPanel {
pub fn new() -> TrackerPanel { pub fn new(tracker_state: Arc<Mutex<TrackerState>>) -> TrackerPanel {
let factory = SignalListItemFactory::new();
factory.connect_setup(move |_, list_item| {
let list_item = list_item
.downcast_ref::<ListItem>()
.expect("Needs to be a List Item");
let label = Label::new(None);
list_item.set_child(Some(&label));
list_item
.property_expression("item")
.chain_property::<StringObject>("string")
.bind(&label, "label", Widget::NONE);
});
let items = StringList::new(&["item1", "item2", "item3"]);
let model = SingleSelection::builder().model(&items).build();
model.connect_selected_item_notify(move |x| {
let item = x
.selected_item()
.and_downcast::<StringObject>();
if let Some(item) = item {
if let Ok(id) = item.string().parse::<u32>() {
debug!("Getting lock on tracker state for setting active tracking id!");
if let Ok(mut ts) = tracker_state.lock() {
ts.tracking_id = id;
}
} else {
error!("An unparsable ID was clicked");
}
} else {
error!("An invalid id was selected from the selection");
}
});
let list_view = ListView::new(Some(model), Some(factory));
let scrolled_window = ScrolledWindow::builder()
.child(&list_view)
.hscrollbar_policy(gtk::PolicyType::Never)
.height_request(200)
.build();
let top_level = Box::builder() let top_level = Box::builder()
.orientation(gtk::Orientation::Vertical) .orientation(gtk::Orientation::Vertical)
@ -28,22 +81,29 @@ impl TrackerPanel {
let enable_disable = ToggleButton::with_label("Enable Automatic Tracking"); let enable_disable = ToggleButton::with_label("Enable Automatic Tracking");
let current_id = Label::builder() let current_id = Label::builder()
.label("") .label("Not Tracking")
.can_focus(false) .can_focus(false)
.can_target(false) .can_target(false)
.css_classes(["current-id"])
.build(); .build();
top_level.append(&enable_disable); top_level.append(&enable_disable);
top_level.append(&current_id); top_level.append(&current_id);
top_level.append(&scrolled_window);
TrackerPanel { TrackerPanel {
top_level, top_level,
enable_disable, enable_disable,
current_id, current_id,
scrolled_window,
items,
list_view,
} }
} }
pub fn get_top_level(&self) -> &Box { pub fn get_top_level(&self) -> &Box {
&self.top_level &self.top_level
} }
@ -51,7 +111,9 @@ impl TrackerPanel {
pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) { pub fn connect_button_callback(&self, to_mec: Sender<ApplicationEvent>) {
self.enable_disable.connect_clicked(move |button| { self.enable_disable.connect_clicked(move |button| {
if let Err(e) = to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active())) { if let Err(e) =
to_mec.send_blocking(ApplicationEvent::EnableAutomatic(button.is_active()))
{
error!("Could not send message to the MEC: {e}"); error!("Could not send message to the MEC: {e}");
} }
}); });

View file

@ -1,3 +1,15 @@
scrolledwindow > listview {
font-size: 10pt;
}
label.current-id {
font-size: 12pt;
color: black;
background-color: cornsilk;
border-radius: 5px;
margin-top: 4px;
margin-bottom: 4px;
}
entry { entry {
font-size: 16pt; font-size: 16pt;
@ -27,5 +39,5 @@ label.JoystickCurrent {
button { button {
color: black; color: black;
font-size: 16pt; font-size: 10pt;
} }