Lev
3 years ago
11 changed files with 6766 additions and 362 deletions
@ -0,0 +1,12 @@
|
||||
[package] |
||||
name = "degeon" |
||||
version = "0.1.0" |
||||
edition = "2021" |
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html |
||||
|
||||
[dependencies] |
||||
iced = { version = "0.3", features = ["glow"] } |
||||
ironforce = { path = "../", features = ["std"] } |
||||
base64 = "0.13.0" |
||||
serde = { version = "1.0" } |
@ -0,0 +1,6 @@
|
||||
#[derive(Clone, Debug)] |
||||
pub enum GuiEvent { |
||||
ChatSelect(usize), |
||||
Typed(String), |
||||
SendClick, |
||||
} |
@ -0,0 +1,70 @@
|
||||
extern crate serde; |
||||
mod message; |
||||
mod state; |
||||
mod gui_events; |
||||
|
||||
use iced::Sandbox; |
||||
use ironforce::res::IFResult; |
||||
use ironforce::{IronForce, Message, MessageType, PublicKey}; |
||||
use crate::state::State; |
||||
|
||||
fn main_if() -> IFResult<()> { |
||||
let ironforce = IronForce::from_file("".to_string())?; |
||||
let if_keys = ironforce.keys.clone(); |
||||
println!( |
||||
"Our public key: {}", |
||||
base64::encode(if_keys.get_public().to_vec().as_slice()) |
||||
); |
||||
let (_thread, if_mutex) = ironforce.launch_main_loop(100); |
||||
let stdin = std::io::stdin(); |
||||
let if_mutex_clone = if_mutex.clone(); |
||||
let if_keys_clone = if_keys.clone(); |
||||
std::thread::spawn(move || loop { |
||||
if let Some(msg) = if_mutex_clone.lock().unwrap().read_message() { |
||||
println!( |
||||
"New message: {}", |
||||
String::from_utf8(msg.get_decrypted(&if_keys_clone).unwrap()).unwrap() |
||||
); |
||||
} |
||||
std::thread::sleep(std::time::Duration::from_millis(300)) |
||||
}); |
||||
loop { |
||||
let mut buf = String::new(); |
||||
stdin.read_line(&mut buf)?; |
||||
let msg_base = if buf.starts_with('>') { |
||||
let target_base64 = buf |
||||
.split(')') |
||||
.next() |
||||
.unwrap() |
||||
.trim_start_matches(">(") |
||||
.to_string(); |
||||
let target = if let Ok(res) = base64::decode(target_base64) { |
||||
res |
||||
} else { |
||||
println!("Wrong b64."); |
||||
continue; |
||||
}; |
||||
buf = buf |
||||
.split(')') |
||||
.skip(1) |
||||
.map(|s| s.to_string()) |
||||
.collect::<Vec<String>>() |
||||
.join(")"); |
||||
Message::build() |
||||
.message_type(MessageType::SingleCast) |
||||
.recipient(&PublicKey::from_vec(target).unwrap()) |
||||
} else { |
||||
Message::build().message_type(MessageType::Broadcast) |
||||
}; |
||||
if_mutex |
||||
.lock() |
||||
.unwrap() |
||||
.send_to_all(msg_base.content(buf.into_bytes()).sign(&if_keys).build()?)?; |
||||
} |
||||
} |
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> { |
||||
// let ironforce = IronForce::from_file("".to_string()).unwrap();
|
||||
// let _if_keys = ironforce.keys.clone();
|
||||
Ok(State::run(iced::Settings::default())?) |
||||
} |
@ -0,0 +1,14 @@
|
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum Message { |
||||
Text(String), |
||||
File(Vec<u8>), |
||||
Service(ServiceMsg), |
||||
} |
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)] |
||||
pub enum ServiceMsg { |
||||
NameRequest, |
||||
NameStatement(String), |
||||
} |
@ -0,0 +1,270 @@
|
||||
use crate::gui_events::GuiEvent; |
||||
use crate::message::Message; |
||||
use core::default::Default; |
||||
use iced::{ |
||||
button, Align, Button, Column, Element, HorizontalAlignment, Length, Row, Sandbox, Settings, |
||||
Text, TextInput, VerticalAlignment, |
||||
}; |
||||
use ironforce::{Keys, PublicKey}; |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
#[derive(Clone, Debug)] |
||||
pub struct Chat { |
||||
pkey: PublicKey, |
||||
messages: Vec<(bool, Message)>, |
||||
name: String, |
||||
scrolled: f32, |
||||
pub input: String, |
||||
} |
||||
|
||||
pub fn view_message(msg: &(bool, Message)) -> Option<Element<GuiEvent>> { |
||||
let msg = &msg.1; |
||||
match msg { |
||||
Message::Text(t) => Some( |
||||
iced::Container::new(Text::new(t.as_str())) |
||||
.padding(10) |
||||
.style(style::Container::Message) |
||||
.into(), |
||||
), |
||||
Message::File(_) => None, |
||||
Message::Service(_) => None, |
||||
} |
||||
} |
||||
|
||||
mod style { |
||||
use iced::container::Style; |
||||
use iced::{button, Background, Color, Vector}; |
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)] |
||||
pub enum Button { |
||||
Primary, |
||||
Secondary, |
||||
Destructive, |
||||
InactiveChat, |
||||
} |
||||
|
||||
impl button::StyleSheet for Button { |
||||
fn active(&self) -> button::Style { |
||||
button::Style { |
||||
background: Some(Background::Color(match self { |
||||
Button::Primary => Color::from_rgb(0.11, 0.35, 0.75), |
||||
Button::Secondary => Color::from_rgb(0.3, 0.1, 0.7), |
||||
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), |
||||
Button::InactiveChat => Color::from_rgb(0.3, 0.52, 0.9), |
||||
})), |
||||
border_radius: 5.0, |
||||
shadow_offset: Vector::new(1.0, 1.0), |
||||
text_color: if self != &Button::InactiveChat { Color::WHITE } else { Color::BLACK }, |
||||
..button::Style::default() |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub enum Container { |
||||
Primary, |
||||
Message, |
||||
} |
||||
|
||||
impl iced::container::StyleSheet for Container { |
||||
fn style(&self) -> Style { |
||||
iced::container::Style { |
||||
text_color: Some(Color::WHITE), |
||||
background: Some(Background::Color(match self { |
||||
Container::Primary => Color::from_rgb(18. / 256., 25. / 256., 70. / 256.), |
||||
Container::Message => Color::from_rgb(0., 0.1, 0.8), |
||||
})), |
||||
border_radius: 5.0, |
||||
border_width: 0.6, |
||||
border_color: Color::TRANSPARENT, |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Chat { |
||||
pub fn header<'a>(name: String) -> Element<'a, GuiEvent> { |
||||
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE)) |
||||
.style(style::Container::Primary) |
||||
.width(Length::Fill) |
||||
.height(Length::Units(50)) |
||||
.padding(10) |
||||
.into() |
||||
} |
||||
|
||||
pub fn send_field<'a>( |
||||
input: String, |
||||
text_input_state: &'a mut iced::text_input::State, |
||||
send_button_state: &'a mut iced::button::State, |
||||
) -> Element<'a, GuiEvent> { |
||||
Row::new() |
||||
.width(Length::Fill) |
||||
.padding(15) |
||||
.push( |
||||
TextInput::new(text_input_state, "Message", input.as_str(), |st| { |
||||
GuiEvent::Typed(st) |
||||
}) |
||||
.padding(8) |
||||
.width(Length::Fill), |
||||
) |
||||
.push( |
||||
Button::new(send_button_state, Text::new("Send")) |
||||
.on_press(GuiEvent::SendClick) |
||||
.style(style::Button::Secondary) |
||||
.padding(20) |
||||
.width(Length::Units(80)), |
||||
) |
||||
.spacing(25) |
||||
.height(Length::Units(100)) |
||||
.into() |
||||
} |
||||
|
||||
pub fn preview<'a>(&'a self, state: &'a mut button::State, i: usize, is_selected: bool) -> Element<'a, GuiEvent> { |
||||
Button::new(state, Text::new(self.name.as_str())) |
||||
.width(Length::Fill) |
||||
.padding(10) |
||||
.style(if is_selected { style::Button::Primary } else { style::Button::InactiveChat }) |
||||
.on_press(GuiEvent::ChatSelect(i)) |
||||
.into() |
||||
} |
||||
|
||||
pub fn view<'a>( |
||||
&'a self, |
||||
text_input_state: &'a mut iced::text_input::State, |
||||
send_button_state: &'a mut iced::button::State, |
||||
) -> Element<'a, GuiEvent> { |
||||
let msgs = self.messages.iter().filter_map(view_message).collect(); |
||||
Column::new() |
||||
.align_items(Align::End) |
||||
.height(Length::Fill) |
||||
.width(Length::FillPortion(4)) |
||||
.push(Self::header(self.name.clone())) |
||||
.push( |
||||
Column::with_children(msgs) |
||||
.padding(20) |
||||
.spacing(10) |
||||
.align_items(Align::End) |
||||
.height(Length::FillPortion(9)), |
||||
) |
||||
.spacing(10) |
||||
.push(Self::send_field( |
||||
self.input.to_string(), |
||||
text_input_state, |
||||
send_button_state, |
||||
)) |
||||
.into() |
||||
} |
||||
|
||||
pub fn example(i: usize) -> Chat { |
||||
Self { |
||||
pkey: Keys::generate().get_public(), |
||||
messages: vec![(false, Message::Text(format!("Example message {}", i)))], |
||||
name: format!("Example user ({})", i), |
||||
scrolled: 0.0, |
||||
input: "".to_string(), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Default, Clone, Debug)] |
||||
pub struct State { |
||||
chats: Vec<Chat>, |
||||
my_name: String, |
||||
selected_chat: usize, |
||||
pub send_button_state: iced::button::State, |
||||
text_input_state: iced::text_input::State, |
||||
preview_button_states: Vec<button::State>, |
||||
} |
||||
|
||||
impl State { |
||||
fn chat_list<'a>( |
||||
chats: &'a Vec<Chat>, |
||||
preview_button_states: &'a mut Vec<button::State>, |
||||
selected: usize |
||||
) -> Element<'a, GuiEvent> { |
||||
Column::with_children( |
||||
chats |
||||
.iter() |
||||
.zip(preview_button_states.iter_mut()) |
||||
.enumerate() |
||||
.map(|(i, (chat, state))| chat.preview(state, i, i == selected)) |
||||
.collect(), |
||||
) |
||||
.padding(20) |
||||
.spacing(10) |
||||
.align_items(Align::Start) |
||||
.width(Length::FillPortion(1)) |
||||
.into() |
||||
} |
||||
|
||||
pub fn active_chat<'a>( |
||||
chats: &'a Vec<Chat>, |
||||
selected_chat: usize, |
||||
send_button_state: &'a mut button::State, |
||||
text_input_state: &'a mut iced::text_input::State, |
||||
) -> Element<'a, GuiEvent> { |
||||
if selected_chat >= chats.len() { |
||||
Text::new("No chat") |
||||
.horizontal_alignment(HorizontalAlignment::Center) |
||||
.vertical_alignment(VerticalAlignment::Center) |
||||
.width(Length::FillPortion(4)) |
||||
.into() |
||||
} else { |
||||
chats[selected_chat].view(text_input_state, send_button_state) |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl Sandbox for State { |
||||
type Message = GuiEvent; |
||||
|
||||
fn new() -> Self { |
||||
let mut st = Self::default(); |
||||
st.chats = vec![Chat::example(1), Chat::example(2)]; |
||||
st.preview_button_states = vec![Default::default(), Default::default()]; |
||||
st |
||||
} |
||||
|
||||
fn title(&self) -> String { |
||||
String::from("Degeon") |
||||
} |
||||
|
||||
fn update(&mut self, message: GuiEvent) { |
||||
match message { |
||||
GuiEvent::ChatSelect(i) => self.selected_chat = i, |
||||
GuiEvent::Typed(st) => self.chats[self.selected_chat].input = st, |
||||
GuiEvent::SendClick => { |
||||
if self.chats[self.selected_chat].input.is_empty() { |
||||
return; |
||||
} |
||||
let new_msg = Message::Text(self.chats[self.selected_chat].input.clone()); |
||||
self.chats[self.selected_chat].input = String::new(); |
||||
self.chats[self.selected_chat] |
||||
.messages |
||||
.push((true, new_msg)); |
||||
// todo
|
||||
} |
||||
} |
||||
} |
||||
|
||||
fn view(&mut self) -> Element<GuiEvent> { |
||||
let Self { |
||||
chats, |
||||
selected_chat, |
||||
send_button_state, |
||||
text_input_state, |
||||
preview_button_states, |
||||
.. |
||||
} = self; |
||||
Row::new() |
||||
.padding(20) |
||||
.push(Self::chat_list(chats, preview_button_states, *selected_chat)) |
||||
.push(Self::active_chat( |
||||
chats, |
||||
*selected_chat, |
||||
send_button_state, |
||||
text_input_state, |
||||
)) |
||||
.height(Length::Fill) |
||||
.into() |
||||
} |
||||
} |
Loading…
Reference in new issue