diff options
Diffstat (limited to 'gui/src/app/mod.rs')
-rw-r--r-- | gui/src/app/mod.rs | 262 |
1 files changed, 212 insertions, 50 deletions
diff --git a/gui/src/app/mod.rs b/gui/src/app/mod.rs index 8fefdbc..77f6c9d 100644 --- a/gui/src/app/mod.rs +++ b/gui/src/app/mod.rs @@ -1,10 +1,12 @@ use std::sync::Arc; +use std::sync::RwLock as StdRwLock; use anyhow::Result; use iced::Application; use iced::Column; use iced::Container; use iced::Length; +use iced::Row; use iced::Scrollable; use iced::TextInput; use iced::scrollable; @@ -17,16 +19,24 @@ use crate::timeline::PostLoadingRecipe; mod message; pub use message::Message; +use crate::gossip::GossipRecipe; + #[derive(Debug)] enum Distrox { - Loading, + Loading { + gossip_subscription_recv: StdRwLock<tokio::sync::oneshot::Receiver<GossipRecipe>>, + }, Loaded { profile: Arc<Profile>, + gossip_subscription_recv: StdRwLock<tokio::sync::oneshot::Receiver<GossipRecipe>>, scroll: scrollable::State, input: text_input::State, input_value: String, timeline: Timeline, + + log_visible: bool, + log: std::collections::VecDeque<String>, }, FailedToStart, } @@ -37,15 +47,33 @@ impl Application for Distrox { type Flags = String; fn new(name: String) -> (Self, iced::Command<Self::Message>) { + let (gossip_subscription_sender, gossip_subscription_recv) = tokio::sync::oneshot::channel(); ( - Distrox::Loading, + Distrox::Loading { + gossip_subscription_recv: StdRwLock::new(gossip_subscription_recv), + }, + iced::Command::perform(async move { - match Profile::load(&name).await { - Err(e) => Message::FailedToLoad(e.to_string()), - Ok(instance) => { - Message::Loaded(Arc::new(instance)) - } + let profile = match Profile::load(&name).await { + Err(e) => return Message::FailedToLoad(e.to_string()), + Ok(instance) => Arc::new(instance), + }; + + if let Err(e) = profile.client() + .pubsub_subscribe("distrox".to_string()) + .await + .map_err(anyhow::Error::from) + .map(|stream| { + log::trace!("Subscription to 'distrox' pubsub channel worked"); + GossipRecipe::new(profile.clone(), stream) + }) + .and_then(|s| gossip_subscription_sender.send(s).map_err(|_| anyhow::anyhow!("Failed to initialize gossipping module"))) + { + log::error!("Failed to load gossip recipe"); + return Message::FailedToLoad(e.to_string()) } + + Message::Loaded(profile) }, |m: Message| -> Message { m }) ) } @@ -55,33 +83,33 @@ impl Application for Distrox { } fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> { + log::trace!("Received message: {}", message.description()); match self { - Distrox::Loading => { - match message { - Message::Loaded(profile) => { - *self = Distrox::Loaded { - profile, - scroll: scrollable::State::default(), - input: text_input::State::default(), - input_value: String::default(), - timeline: Timeline::new(), - }; - } + Distrox::Loading { gossip_subscription_recv } => { + if let Message::Loaded(profile) = message { + *self = Distrox::Loaded { + profile, + + // Don't even try to think what hoops I am jumping through here... + gossip_subscription_recv: std::mem::replace(gossip_subscription_recv, StdRwLock::new(tokio::sync::oneshot::channel().1)), + scroll: scrollable::State::default(), + input: text_input::State::default(), + input_value: String::default(), + timeline: Timeline::new(), + log_visible: false, + log: std::collections::VecDeque::with_capacity(1000), + }; - Message::FailedToLoad(e) => { - log::error!("Failed to load: {}", e); - *self = Distrox::FailedToStart; - } - - _ => {} } - } + iced::Command::none() + }, - Distrox::Loaded { profile, ref mut input_value, timeline, .. } => { + Distrox::Loaded { profile, ref mut input_value, timeline, log_visible, log, .. } => { match message { Message::InputChanged(input) => { *input_value = input; + iced::Command::none() } Message::CreatePost => { @@ -96,32 +124,84 @@ impl Application for Distrox { |res| match res { Ok(cid) => Message::PostCreated(cid), Err(e) => Message::PostCreationFailed(e.to_string()) - }); + }) + } else { + iced::Command::none() } } Message::PostCreated(cid) => { *input_value = String::new(); log::info!("Post created: {}", cid); + iced::Command::none() } Message::PostCreationFailed(err) => { log::error!("Post creation failed: {}", err); + iced::Command::none() } Message::PostLoaded((payload, content)) => { timeline.push(payload, content); + iced::Command::none() } Message::PostLoadingFailed => { log::error!("Failed to load some post, TODO: Better error logging"); + iced::Command::none() } Message::TimelineScrolled(f) => { log::trace!("Timeline scrolled: {}", f); + iced::Command::none() + } + + Message::ToggleLog => { + log::trace!("Log toggled"); + *log_visible = !*log_visible; + iced::Command::none() } - _ => {} + Message::GossipHandled(msg) => { + use distrox_lib::gossip::GossipMessage; + + log::trace!("Gossip handled, adding to log: {:?}", msg); + let msg = match msg { + GossipMessage::CurrentProfileState { peer_id, cid } => { + format!("Peer {:?} is at {:?}", peer_id, cid) + } + }; + log.push_back(msg); + while log.len() > 1000 { + let _ = log.pop_front(); + } + iced::Command::none() + } + + Message::PublishGossipAboutMe => { + let profile = profile.clone(); + iced::Command::perform(async move { + if let Err(e) = profile.gossip_own_state("distrox".to_string()).await { + Message::GossippingFailed(e.to_string()) + } else { + Message::OwnStateGossipped + } + }, |m: Message| -> Message { m }) + } + + Message::OwnStateGossipped => { + log::trace!("Gossipped own state"); + log.push_back("Gossipped own state".to_string()); + iced::Command::none() + } + + Message::GossippingFailed(e) => { + log::trace!("Gossipped failed: {}", e); + log.push_back(format!("Gossipped failed: {}", e)); + iced::Command::none() + } + + _ => iced::Command::none(), } } @@ -129,43 +209,79 @@ impl Application for Distrox { unimplemented!() } } - iced::Command::none() } fn view(&mut self) -> iced::Element<Self::Message> { match self { - Distrox::Loading => { + Distrox::Loading { .. } => { let text = iced::Text::new("Loading"); let content = Column::new() - .max_width(800) .spacing(20) .push(text); Container::new(content) .width(Length::Fill) + .height(Length::Fill) .center_x() + .center_y() .into() } - Distrox::Loaded { input, input_value, timeline, scroll, .. } => { - let input = TextInput::new( - input, - "What do you want to tell the world?", - input_value, - Message::InputChanged, - ) - .padding(15) - .size(12) - .on_submit(Message::CreatePost); - - let timeline = timeline.view(); - - Scrollable::new(scroll) - .padding(40) - .push(input) - .push(timeline) - .into() + Distrox::Loaded { input, input_value, timeline, scroll, log_visible, log, .. } => { + let left_column = Column::new() + .into(); + + let mid_column = Column::new() + .push({ + let input = TextInput::new( + input, + "What do you want to tell the world?", + input_value, + Message::InputChanged, + ) + .padding(15) + .size(12) + .on_submit(Message::CreatePost); + + let timeline = timeline.view(); + + Scrollable::new(scroll) + .padding(40) + .push(input) + .push(timeline) + }) + .into(); + + let right_column = Column::new() + .into(); + + let content = Row::with_children(vec![ + left_column, + mid_column, + right_column + ]) + .spacing(20) + .height(Length::Fill) + .width(Length::Fill); + + let content = Column::new() + .height(Length::Fill) + .width(Length::Fill) + .push(content); + + if *log_visible { + let log = Column::with_children({ + log.iter() + .map(iced::Text::new) + .map(|txt| txt.size(8)) + .map(iced::Element::from) + .collect() + }); + content.push(log) + } else { + content + }.into() } Distrox::FailedToStart => { @@ -175,7 +291,7 @@ impl Application for Distrox { } fn subscription(&self) -> iced::Subscription<Self::Message> { - match self { + let post_loading_subs = match self { Distrox::Loaded { profile, .. } => { let head = profile.head(); @@ -189,7 +305,53 @@ impl Application for Distrox { } } _ => iced::Subscription::none(), + }; + + let keyboard_subs = { + use iced_native::event::Event; + + iced_native::subscription::events_with(|event, _| { + match event { + Event::Keyboard(iced_native::keyboard::Event::KeyPressed { key_code, .. }) => { + if key_code == iced_native::keyboard::KeyCode::F11 { + Some(Message::ToggleLog) + } else { + None + } + }, + _ => None, + } + }) + }; + + let gossip_sub = match self { + Distrox::Loaded { gossip_subscription_recv, .. } => { + match gossip_subscription_recv.write().ok() { + Some(mut sub) => sub.try_recv() + .ok() // Either empty or closed, ignore both + .map(|sub| iced::Subscription::from_recipe(sub)), + None => None + } + }, + _ => None, + }; + + let gossip_sending_sub = { + iced::time::every(std::time::Duration::from_millis(100)) + .map(|_| Message::PublishGossipAboutMe) + }; + + let mut subscriptions = vec![ + post_loading_subs, + keyboard_subs, + gossip_sending_sub, + ]; + + if let Some(gossip_sub) = gossip_sub { + subscriptions.push(gossip_sub); } + + iced::Subscription::batch(subscriptions) } } |