summaryrefslogtreecommitdiffstats
path: root/gui/src/app/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gui/src/app/mod.rs')
-rw-r--r--gui/src/app/mod.rs262
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)
}
}