481 lines
16 KiB
Rust
481 lines
16 KiB
Rust
use iced::widget::{button, column, row, text};
|
|
use iced::widget::text_input;
|
|
use iced::executor;
|
|
use iced::keyboard;
|
|
use iced::subscription;
|
|
use iced::{Alignment, Element, Application, Settings, Subscription, Theme, Command};
|
|
use iced_futures::backend::native::async_std::time::every;
|
|
use handlebars::Handlebars;
|
|
use serde_json::{json, Value};
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
use std::thread;
|
|
use reqwest;
|
|
|
|
use crate::stream::OutputStream;
|
|
use crate::config::{GUI,GUIFeedback};
|
|
|
|
pub fn main(cfg: GUI, output_stream: Box<dyn OutputStream>) -> iced::Result {
|
|
let def: iced::Settings<()> = Settings::default();
|
|
let settings = Settings{
|
|
flags: Flags {
|
|
output_stream: output_stream,
|
|
cfg: cfg,
|
|
},
|
|
antialiasing: def.antialiasing,
|
|
default_font: def.default_font,
|
|
default_text_size: def.default_text_size,
|
|
exit_on_close_request: def.exit_on_close_request,
|
|
id: def.id,
|
|
text_multithreading: def.text_multithreading,
|
|
try_opengles_first: def.try_opengles_first,
|
|
window: iced::window::Settings{
|
|
size: (300, 720),
|
|
position: iced::window::Position::Specific(0, 0),
|
|
..iced::window::Settings::default()
|
|
},
|
|
};
|
|
Main::run(settings)
|
|
}
|
|
|
|
struct Main {
|
|
feedback_recv_c: std::sync::mpsc::Receiver<Feedback>,
|
|
feedback_send_c: std::sync::mpsc::Sender<Feedback>,
|
|
ntfy_from_client: String,
|
|
ntfy_from_server: String,
|
|
configuring: Option<Message>,
|
|
inputs: Inputs,
|
|
keys_newly_down: Vec<iced::keyboard::KeyCode>,
|
|
keys_already_down: Vec<iced::keyboard::KeyCode>,
|
|
keys_up: Vec<iced::keyboard::KeyCode>,
|
|
flags: Flags,
|
|
input_text_entry_instruction: String,
|
|
input_text_entry_value: String,
|
|
}
|
|
|
|
struct Flags {
|
|
output_stream: Box<dyn OutputStream>,
|
|
cfg: GUI,
|
|
}
|
|
|
|
struct Inputs {
|
|
stick: Stick,
|
|
a: iced::keyboard::KeyCode,
|
|
b: iced::keyboard::KeyCode,
|
|
x: iced::keyboard::KeyCode,
|
|
y: iced::keyboard::KeyCode,
|
|
l: iced::keyboard::KeyCode,
|
|
r: iced::keyboard::KeyCode,
|
|
}
|
|
|
|
struct Stick {
|
|
up: iced::keyboard::KeyCode,
|
|
down: iced::keyboard::KeyCode,
|
|
left: iced::keyboard::KeyCode,
|
|
right: iced::keyboard::KeyCode,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Message {
|
|
EventOccurred(iced_native::Event),
|
|
Tick,
|
|
InputTextEntryUpdate(String),
|
|
InputTextEntrySubmitSay,
|
|
InputTextEntrySubmitSend,
|
|
Up,
|
|
Down,
|
|
Left,
|
|
Right,
|
|
A,
|
|
B,
|
|
X,
|
|
Y,
|
|
L,
|
|
R,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum Feedback {
|
|
Heard(String),
|
|
Say(String),
|
|
Send(String),
|
|
}
|
|
|
|
fn controller_button_to_string(btn: Message, cur: iced::keyboard::KeyCode) -> String {
|
|
return format!("{:?} => {:?}", cur, btn);
|
|
}
|
|
|
|
impl Main {
|
|
fn key_code_to_string(&self, key_code: &iced::keyboard::KeyCode) -> Option<&String> {
|
|
match key_code {
|
|
_ if key_code == &self.inputs.stick.up => Some(&self.flags.cfg.buttons.up),
|
|
_ if key_code == &self.inputs.stick.down => Some(&self.flags.cfg.buttons.down),
|
|
_ if key_code == &self.inputs.stick.left => Some(&self.flags.cfg.buttons.left),
|
|
_ if key_code == &self.inputs.stick.right => Some(&self.flags.cfg.buttons.right),
|
|
_ if key_code == &self.inputs.a => Some(&self.flags.cfg.buttons.a),
|
|
_ if key_code == &self.inputs.b => Some(&self.flags.cfg.buttons.b),
|
|
_ if key_code == &self.inputs.x => Some(&self.flags.cfg.buttons.x),
|
|
_ if key_code == &self.inputs.y => Some(&self.flags.cfg.buttons.y),
|
|
_ if key_code == &self.inputs.l => Some(&self.flags.cfg.buttons.l),
|
|
_ if key_code == &self.inputs.r => Some(&self.flags.cfg.buttons.r),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn send_from_client(&mut self, text: Feedback) {
|
|
match text.clone() {
|
|
Feedback::Say(s) | Feedback::Send(s) if s.len() > 0 && s.len() < 1000 => {},
|
|
_ => return,
|
|
};
|
|
match self.feedback_send_c.send(text) {
|
|
Ok(_) => {},
|
|
Err(err) => eprintln!("main.send_say() failed to enqueue: {}", err),
|
|
};
|
|
}
|
|
|
|
fn exchange(&mut self) {
|
|
self.exchange_send();
|
|
self.exchange_recv();
|
|
}
|
|
|
|
fn exchange_recv(&mut self) {
|
|
loop {
|
|
match self.feedback_recv_c.try_recv() {
|
|
Ok(msg) => {
|
|
match msg {
|
|
Feedback::Heard(msg) => self.ntfy_from_server = msg,
|
|
_ => break,
|
|
};
|
|
},
|
|
_ => break,
|
|
};
|
|
}
|
|
}
|
|
|
|
fn exchange_send(&mut self) {
|
|
let mut s = vec![];
|
|
for key_code in self.keys_newly_down.iter() {
|
|
match self.key_code_to_string(key_code) {
|
|
Some(x) => {
|
|
for c in x.chars() {
|
|
s.push(c);
|
|
}
|
|
self.keys_already_down.push(*key_code);
|
|
},
|
|
None => {},
|
|
};
|
|
}
|
|
let mut t = vec![];
|
|
self.keys_newly_down.clear();
|
|
for key_code in self.keys_up.iter() {
|
|
match self.key_code_to_string(key_code) {
|
|
Some(x) => {
|
|
for c in x.chars() {
|
|
for c in self.flags.cfg.release_prefix.chars() {
|
|
t.push(c);
|
|
}
|
|
t.push(c);
|
|
}
|
|
},
|
|
None => {},
|
|
};
|
|
}
|
|
if t.len() + s.len() > 0 {
|
|
self.flags.output_stream.put(self.sprintf_pressed_released(s, t));
|
|
}
|
|
self.keys_up.clear();
|
|
}
|
|
|
|
fn sprintf(&self, format: &String, content: &Value) -> Vec<char> {
|
|
return Handlebars::new().render_template(format, content).unwrap().chars().collect();
|
|
}
|
|
|
|
fn sprintf_pressed_released(&self, pressed: Vec<char>, released: Vec<char>) -> Vec<char> {
|
|
match self.flags.cfg.format_keys.clone() {
|
|
Some(x) => {
|
|
return self.sprintf(&x, &json!({
|
|
"pressed": pressed.iter().collect::<String>(),
|
|
"released": released.iter().collect::<String>(),
|
|
"ms": SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(),
|
|
"user": self.flags.cfg.user,
|
|
}));
|
|
},
|
|
None => {
|
|
let mut combo = vec![];
|
|
combo.extend(pressed);
|
|
combo.extend(released);
|
|
return combo;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Application for Main {
|
|
type Message = Message;
|
|
type Flags = Flags;
|
|
type Theme = Theme;
|
|
type Executor = executor::Default;
|
|
|
|
fn new(flags: Self::Flags) -> (Self, Command<Message>) {
|
|
let (sender1, receiver1) = std::sync::mpsc::channel();
|
|
let (sender2, receiver2) = std::sync::mpsc::channel();
|
|
let feedback_cfg = flags.cfg.feedback.clone();
|
|
thread::spawn(move || {
|
|
Feedbacker{
|
|
send_c: sender1,
|
|
recv_c: receiver2,
|
|
cfg: feedback_cfg,
|
|
}.listen()
|
|
});
|
|
return (Self {
|
|
feedback_send_c: sender2,
|
|
feedback_recv_c: receiver1,
|
|
ntfy_from_client: String::from(" "),
|
|
ntfy_from_server: String::from(" "),
|
|
configuring: None,
|
|
inputs: Inputs{
|
|
stick: Stick {
|
|
up: iced::keyboard::KeyCode::W,
|
|
down: iced::keyboard::KeyCode::S,
|
|
left: iced::keyboard::KeyCode::A,
|
|
right: iced::keyboard::KeyCode::D,
|
|
},
|
|
a: iced::keyboard::KeyCode::Key1,
|
|
b: iced::keyboard::KeyCode::Key2,
|
|
x: iced::keyboard::KeyCode::Key3,
|
|
y: iced::keyboard::KeyCode::Key4,
|
|
l: iced::keyboard::KeyCode::Q,
|
|
r: iced::keyboard::KeyCode::E,
|
|
},
|
|
flags: flags,
|
|
keys_newly_down: vec![],
|
|
keys_already_down: vec![],
|
|
keys_up: vec![],
|
|
input_text_entry_instruction: String::from(""),
|
|
input_text_entry_value: String::from(""),
|
|
}, Command::none())
|
|
}
|
|
|
|
fn title(&self) -> String {
|
|
return String::from("Rusty Pipe")
|
|
}
|
|
|
|
fn update(&mut self, msg: Message) -> Command<Message> {
|
|
match msg.clone() {
|
|
Message::Tick => {
|
|
self.exchange();
|
|
},
|
|
Message::InputTextEntryUpdate(payload) => {
|
|
self.input_text_entry_value = payload;
|
|
},
|
|
Message::InputTextEntrySubmitSend => {
|
|
self.send_from_client(Feedback::Send(self.input_text_entry_value.clone()));
|
|
self.input_text_entry_value = String::from("");
|
|
},
|
|
Message::InputTextEntrySubmitSay => {
|
|
self.send_from_client(Feedback::Say(self.input_text_entry_value.clone()));
|
|
self.input_text_entry_value = String::from("");
|
|
},
|
|
Message::EventOccurred(event) if self.configuring.is_some() => {
|
|
match event {
|
|
iced::event::Event::Keyboard(keyboard::Event::KeyPressed{
|
|
key_code,
|
|
modifiers: _,
|
|
..
|
|
}) => {
|
|
match self.configuring.as_ref().unwrap() {
|
|
Message::Up => { self.inputs.stick.up = key_code },
|
|
Message::Down => { self.inputs.stick.down = key_code },
|
|
Message::Left => { self.inputs.stick.left = key_code },
|
|
Message::Right => { self.inputs.stick.right = key_code },
|
|
Message::A => { self.inputs.a = key_code },
|
|
Message::B => { self.inputs.b = key_code },
|
|
Message::X => { self.inputs.x = key_code },
|
|
Message::Y => { self.inputs.y = key_code },
|
|
Message::L => { self.inputs.l = key_code },
|
|
Message::R => { self.inputs.r = key_code },
|
|
_ => {},
|
|
};
|
|
self.ntfy_from_client = format!("{:?} => {:?}", key_code.clone(), self.configuring.as_ref().unwrap());
|
|
self.configuring = None;
|
|
},
|
|
_ => {},
|
|
};
|
|
},
|
|
Message::EventOccurred(event) => {
|
|
match event {
|
|
iced::event::Event::Keyboard(keyboard::Event::KeyPressed{
|
|
key_code,
|
|
modifiers: _,
|
|
..
|
|
}) => {
|
|
match self.keys_already_down.iter().position(|x| *x == key_code) {
|
|
Some(_) => {},
|
|
None => {
|
|
self.keys_newly_down.push(key_code);
|
|
self.keys_newly_down.dedup();
|
|
},
|
|
};
|
|
self.exchange();
|
|
},
|
|
iced::event::Event::Keyboard(keyboard::Event::KeyReleased{
|
|
key_code,
|
|
..
|
|
}) => {
|
|
match self.keys_already_down.iter().position(|x| *x == key_code) {
|
|
Some(idx) => {
|
|
self.keys_already_down.remove(idx);
|
|
self.keys_up.push(key_code);
|
|
},
|
|
None => {},
|
|
};
|
|
match self.keys_newly_down.iter().position(|x| *x == key_code) {
|
|
Some(idx) => {
|
|
self.keys_newly_down.remove(idx);
|
|
self.keys_up.push(key_code);
|
|
},
|
|
None => {},
|
|
};
|
|
self.exchange();
|
|
},
|
|
_ => {},
|
|
};
|
|
},
|
|
_ => {
|
|
self.configuring = Some(msg.clone());
|
|
self.ntfy_from_client = format!("push a key to bind to {:?}", msg.clone());
|
|
},
|
|
}
|
|
return Command::none();
|
|
}
|
|
|
|
fn subscription(&self) -> Subscription<Message> {
|
|
return subscription::Subscription::batch(vec![
|
|
subscription::events_with(|event, status| match status {
|
|
iced::event::Status::Ignored => match event {
|
|
iced::Event::Keyboard(_) => Some(Message::EventOccurred(event)),
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
}),
|
|
every(std::time::Duration::from_millis(5000)).map(|_| Message::Tick),
|
|
]);
|
|
}
|
|
|
|
fn view(&self) -> Element<Message> {
|
|
let new_cfg_button = |msg: Message, s| button(text(controller_button_to_string(msg.clone(), s))).on_press(msg.clone());
|
|
return column![
|
|
text(String::from("= MAYHEM PARTY =")).size(32),
|
|
column![
|
|
column![
|
|
text(String::from("Button Mapping")).size(24),
|
|
text(String::from("--------------")).size(24),
|
|
new_cfg_button(Message::Up, self.inputs.stick.up),
|
|
new_cfg_button(Message::Down, self.inputs.stick.down),
|
|
new_cfg_button(Message::Left, self.inputs.stick.left),
|
|
new_cfg_button(Message::Right, self.inputs.stick.right),
|
|
new_cfg_button(Message::A, self.inputs.a),
|
|
new_cfg_button(Message::B, self.inputs.b),
|
|
new_cfg_button(Message::X, self.inputs.x),
|
|
new_cfg_button(Message::Y, self.inputs.y),
|
|
new_cfg_button(Message::L, self.inputs.l),
|
|
new_cfg_button(Message::R, self.inputs.r),
|
|
text(String::from("--------------")).size(24),
|
|
text(self.ntfy_from_client.clone()).size(18),
|
|
].padding(20).align_items(Alignment::Center),
|
|
column![
|
|
text_input(
|
|
&self.input_text_entry_instruction,
|
|
&self.input_text_entry_value,
|
|
Message::InputTextEntryUpdate
|
|
),
|
|
row![
|
|
button(text("Say")).on_press(Message::InputTextEntrySubmitSay).padding(20),
|
|
button(text("Send")).on_press(Message::InputTextEntrySubmitSend).padding(20),
|
|
].padding(20).align_items(Alignment::Center),
|
|
text(self.ntfy_from_server.clone()).size(18),
|
|
].padding(20).align_items(Alignment::Center),
|
|
].padding(0).align_items(Alignment::Center),
|
|
].padding(0).align_items(Alignment::Center).into();
|
|
}
|
|
}
|
|
|
|
struct Feedbacker {
|
|
send_c: std::sync::mpsc::Sender<Feedback>,
|
|
recv_c: std::sync::mpsc::Receiver<Feedback>,
|
|
cfg: GUIFeedback,
|
|
}
|
|
|
|
impl Feedbacker {
|
|
fn listen(&mut self) {
|
|
loop {
|
|
std::thread::sleep(std::time::Duration::from_secs(2));
|
|
match self.read_from_server() {
|
|
Some(msg) if msg.len() > 0 => {
|
|
self.write_from_server(msg.clone());
|
|
},
|
|
_ => {},
|
|
};
|
|
match self.read_from_client() {
|
|
Some(msg) => self.write_from_client(msg.clone()),
|
|
_ => {},
|
|
};
|
|
}
|
|
}
|
|
|
|
fn read_from_server(&mut self) -> Option<String> {
|
|
return match &self.cfg.url_read {
|
|
Some(url) => {
|
|
match reqwest::blocking::get(url) {
|
|
Ok(resp) => match resp.text() {
|
|
Ok(text) => Some(text),
|
|
_ => None,
|
|
},
|
|
Err(err) => {
|
|
eprintln!("feedback.read: cannot fetch: {}", err);
|
|
None
|
|
},
|
|
}
|
|
},
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
fn write_from_server(&mut self, msg: String) {
|
|
match self.send_c.send(Feedback::Heard(msg.clone())) {
|
|
Ok(_) => {},
|
|
Err(err) => eprintln!("feedback.listen() failed to display {}: {}", msg, err),
|
|
};
|
|
}
|
|
|
|
fn read_from_client(&mut self) -> Option<Feedback> {
|
|
let mut last: Option<Feedback> = None;
|
|
loop {
|
|
match self.recv_c.try_recv() {
|
|
Ok(msg) => {
|
|
last = Some(msg);
|
|
},
|
|
_ => break,
|
|
};
|
|
}
|
|
return last;
|
|
}
|
|
|
|
fn write_from_client(&mut self, msg: Feedback) {
|
|
match msg {
|
|
Feedback::Say(say) | Feedback::Send(say) if say.len() > 0 => match &self.cfg.url_say {
|
|
Some(url) => {
|
|
match reqwest::blocking::get(format!("{}{}", url, say)) {
|
|
Err(err) => {
|
|
eprintln!("feedback.write_from_client: cannot say: {}", err);
|
|
},
|
|
_ => {},
|
|
};
|
|
},
|
|
_ => {},
|
|
},
|
|
_ => {},
|
|
};
|
|
}
|
|
}
|
|
|