diff --git a/Cargo.lock b/Cargo.lock index ddc27f7..41aa957 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -586,6 +586,7 @@ dependencies = [ "iced", "ironforce", "pyo3", + "rand", "serde", "serde_json", ] diff --git a/degeon/src/chat.rs b/degeon/src/chat.rs index cc0973d..7b66382 100644 --- a/degeon/src/chat.rs +++ b/degeon/src/chat.rs @@ -1,9 +1,9 @@ -use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput}; -use iced_native::button; use crate::gui_events::GuiEvent; use crate::state; use crate::styles::style; pub use degeon_core::Chat; +use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput}; +use iced_native::button; pub trait RenderableChat { /// Render header of the chat @@ -29,10 +29,10 @@ pub trait RenderableChat { &'a self, text_input_state: &'a mut iced::text_input::State, send_button_state: &'a mut iced::button::State, + scroll_state: &'a mut iced::scrollable::State, ) -> Element<'a, GuiEvent>; } - impl RenderableChat for Chat { /// Render header of the chat fn header<'a>(name: String) -> Element<'a, GuiEvent> { @@ -96,20 +96,33 @@ impl RenderableChat for Chat { &'a self, text_input_state: &'a mut iced::text_input::State, send_button_state: &'a mut iced::button::State, + scroll_state: &'a mut iced::scrollable::State, ) -> Element<'a, GuiEvent> { let pkey_clone = self.pkey.clone(); - let msgs = self.messages.iter().filter_map(move |msg| state::view_message(msg, pkey_clone.clone())).collect(); + let msgs = self + .messages + .iter() + .filter_map(move |msg| state::view_message(msg, pkey_clone.clone())) + .collect(); Column::new() .align_items(Align::End) .height(Length::Fill) .width(Length::FillPortion(4)) .push(Self::header(self.profile.name.clone())) .push( - Column::with_children(msgs) - .padding(20) - .spacing(10) - .align_items(Align::End) - .height(Length::FillPortion(9)), + iced::Container::new( + iced::Scrollable::new(scroll_state) + .push( + Column::with_children(msgs) + .padding(20) + .spacing(10) + .align_items(Align::End), + ) + .align_items(Align::End) + .width(Length::Fill), + ) + .align_y(Align::End) + .height(Length::FillPortion(9)), ) .spacing(10) .push(Self::send_field( diff --git a/degeon/src/state.rs b/degeon/src/state.rs index 087d7b6..099584c 100644 --- a/degeon/src/state.rs +++ b/degeon/src/state.rs @@ -61,6 +61,8 @@ pub struct DegeonApp { name_input_state: iced::text_input::State, /// Button on the profile screen profile_done_button_state: iced::button::State, + /// Scroll state + scroll: iced::scrollable::State, } impl DegeonApp { @@ -94,6 +96,7 @@ impl DegeonApp { selected_chat: usize, send_button_state: &'a mut button::State, text_input_state: &'a mut iced::text_input::State, + scroll_state: &'a mut iced::scrollable::State, ) -> Element<'a, GuiEvent> { if selected_chat >= chats.len() { Text::new("No chat") @@ -102,7 +105,7 @@ impl DegeonApp { .width(Length::FillPortion(4)) .into() } else { - chats[selected_chat].view(text_input_state, send_button_state) + chats[selected_chat].view(text_input_state, send_button_state, scroll_state) } } } @@ -115,6 +118,7 @@ impl DegeonApp { send_button_state, text_input_state, preview_button_states, + scroll: scroll_state, .. } = self; Row::new() @@ -129,6 +133,7 @@ impl DegeonApp { *selected_chat, send_button_state, text_input_state, + scroll_state, )) .height(Length::Fill) .into() @@ -182,8 +187,12 @@ impl Application for DegeonApp { fn new(_: ()) -> (Self, iced::Command) { let mut data = Degeon::restore_from_file("".to_string()).unwrap(); - data.chats = vec![Chat::example(1, &data.keys), Chat::example(2, &data.keys)]; + if data.chats.is_empty() { + data.chats = vec![Chat::example(1, &data.keys), Chat::example(2, &data.keys)]; + } data.send_multicast(ProtocolMsg::Ping).unwrap(); + let mut scroll: iced::scrollable::State = Default::default(); + scroll.scroll_to(1., iced::Rectangle::with_size(iced::Size::ZERO), iced::Rectangle::with_size(iced::Size::UNIT)); let st = DegeonApp { screen: if data.profile.name.is_empty() { AppScreen::ProfileEditor @@ -197,6 +206,7 @@ impl Application for DegeonApp { preview_button_states: vec![Default::default(), Default::default()], name_input_state: Default::default(), profile_done_button_state: Default::default(), + scroll, }; (st, iced::Command::none()) } diff --git a/degeon_core/Cargo.toml b/degeon_core/Cargo.toml index c9430d5..9683061 100644 --- a/degeon_core/Cargo.toml +++ b/degeon_core/Cargo.toml @@ -13,6 +13,7 @@ serde_json = "1.0.72" futures = "0.3.18" chrono = "0.4.19" iced = { version = "0.3.0", features = ["glow"] } +rand = "0.8.4" [dependencies.pyo3] version = "0.14.0" diff --git a/degeon_core/src/degeon_worker.rs b/degeon_core/src/degeon_worker.rs index f1267e0..3e2299d 100644 --- a/degeon_core/src/degeon_worker.rs +++ b/degeon_core/src/degeon_worker.rs @@ -42,7 +42,7 @@ fn get_initialized_ironforce() -> (Arc>, Keys) { let ironforce = IronForce::from_file("".to_string()).unwrap(); let keys = ironforce.keys.clone(); println!("ID: {}", keys.get_public().get_short_id()); - let (_thread, ironforce) = ironforce.launch_main_loop(1000); + let (_thread, ironforce) = ironforce.launch_main_loop(270); (ironforce, keys) } @@ -213,27 +213,44 @@ impl Degeon { #[pymethods] impl Degeon { - /// Create a new text message and send it - pub fn send_text_message(&self, text: String, chat_i: usize) -> PyResult<()> { - self.send_message( - ProtocolMsg::NewMessage(DegMessage::new_text(text, &self.keys.get_public())), - &self.chats[chat_i].pkey, - ) - .map_err(|e| { - pyo3::exceptions::PyTypeError::new_err(format!("There was an error in Rust: {:?}", e)) - }) + /// Create a new text message and send it (in thread) + /// + /// `text` is the content of the message, + /// `chat_i` is the index of the chat in the list + pub fn send_text_message(&mut self, text: String, chat_i: usize) { + let msg = DegMessage::new_text(text, &self.keys.get_public()); + self.chats[chat_i].messages.push(msg.clone()); + let cloned_self = self.clone(); + std::thread::spawn(move || { + cloned_self + .send_message( + ProtocolMsg::NewMessage(msg), + &cloned_self.chats[chat_i].pkey, + ) + .map_err(|e| println!("There was an error in Rust: {:?}", e)) + }); } /// Handle one message pub fn handling_loop_iteration(&mut self) { let event = self.read_message_and_create_event(); if let Some(event) = event { - self.process_event(&event, true).unwrap_or_else(|e| println!("Error: {:?}", e)); + self.process_event(&event, true) + .unwrap_or_else(|e| println!("Error: {:?}", e)); } } + /// Get length of the IF's message queue + /// + /// If IF worker is locked, returns 0 pub fn message_queue_len(&self) -> usize { - self.ironforce.lock().unwrap().messages.len() + self.ironforce.try_lock().map(|r| r.messages.len()).unwrap_or(0) + } + + /// Check if the message was written by the current user (since `PublicKey` isn't a python type). + /// Returns True if the author of the message is the current user + pub fn check_message_ownership(&self, message: &DegMessage) -> bool { + message.sender == self.keys.get_public() } } @@ -293,6 +310,7 @@ impl Degeon { } impl Degeon { + /// Get one message from the IF message queue and process it, resulting in an event pub fn read_message_and_create_event(&self) -> Option { let msg_raw = self.ironforce.lock().unwrap().read_message(); let msg = msg_raw @@ -317,7 +335,7 @@ impl Degeon { } match msg { Some(Some(event)) => Some(event), - _ => None + _ => None, } } } @@ -326,10 +344,19 @@ impl Stream for Degeon { type Item = GuiEvent; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + if self.ironforce.try_lock().is_err() { + return Poll::Ready(Some(GuiEvent::None)); + } let timestamp_0 = std::time::Instant::now(); let msg = self.read_message_and_create_event(); if timestamp_0.elapsed() < std::time::Duration::from_millis(5) { std::thread::sleep(std::time::Duration::from_millis(5)); + if rand::random::() { + return Poll::Pending; + } + } + if timestamp_0.elapsed() > std::time::Duration::from_millis(800) { + println!("Poll_next took {:?}", timestamp_0.elapsed()); } Poll::Ready(Some(msg.unwrap_or(GuiEvent::None))) } diff --git a/degeon_py/button.py b/degeon_py/button.py index 59f525c..55f1cb3 100644 --- a/degeon_py/button.py +++ b/degeon_py/button.py @@ -87,7 +87,7 @@ class Button: # if this is a mouse event if event.type in [pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.MOUSEBUTTONDOWN]: # if the mouse event is inside the button - if self.top_left[0] <= event.pos[0] < self.bottom and self.top_left[1] <= event.pos[1] < self.right: + if self.top_left[0] <= event.pos[0] < self.right and self.top_left[1] <= event.pos[1] < self.bottom: if event.type == pygame.MOUSEBUTTONUP: self.is_pressed = False return True @@ -96,4 +96,6 @@ class Button: self.is_pressed = True elif event.type == pygame.MOUSEMOTION: self.is_hovered = True + else: + self.is_hovered = False return False diff --git a/degeon_py/config.py b/degeon_py/config.py index ff63594..8e42b43 100644 --- a/degeon_py/config.py +++ b/degeon_py/config.py @@ -3,7 +3,7 @@ import pygame pygame.init() -# Fontss +# Fonts font = pygame.font.Font('CRC35.otf', 32) font_large = pygame.font.Font('CRC35.otf', 50) diff --git a/degeon_py/degeon.py b/degeon_py/degeon.py index c7e6fe8..3926081 100644 --- a/degeon_py/degeon.py +++ b/degeon_py/degeon.py @@ -55,7 +55,8 @@ class Degeon: """ Do all the necessary Rust work """ - pass # todo + while self.core.message_queue_len(): + self.core.handling_loop_iteration() def tick(self): """ @@ -78,7 +79,10 @@ class Degeon: else: self.active_chat = None if self.active_chat is not None: - self.active_chat.process_event(event) + # If the result is a string, it's a message + result: typing.Optional[str] = self.active_chat.process_event(event) + if result: + self.core.send_text_message(result, self.chat_selector.active_chat) def main_loop(self, screen: pygame.Surface): """ diff --git a/degeon_py/degeon_core.so b/degeon_py/degeon_core.so new file mode 100755 index 0000000..59cb15e Binary files /dev/null and b/degeon_py/degeon_core.so differ diff --git a/src/bin/worker.rs b/src/bin/worker.rs index 3ac8d1a..3b6042a 100644 --- a/src/bin/worker.rs +++ b/src/bin/worker.rs @@ -8,7 +8,7 @@ fn main() -> IFResult<()> { "Our public key: {}", base64::encode(if_keys.get_public().to_vec().as_slice()) ); - let (_thread, if_mutex) = ironforce.launch_main_loop(100); + let (_thread, if_mutex) = ironforce.launch_main_loop(50); let stdin = std::io::stdin(); let if_mutex_clone = if_mutex.clone(); let if_keys_clone = if_keys.clone(); @@ -19,7 +19,7 @@ fn main() -> IFResult<()> { String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap() ); } - std::thread::sleep(std::time::Duration::from_millis(300)) + std::thread::sleep(std::time::Duration::from_millis(200)) }); loop { let mut buf = String::new();