Compare commits
2 commits
61b5fb1aa7
...
ad5d2f3b37
Author | SHA1 | Date | |
---|---|---|---|
ad5d2f3b37 | |||
6d2b16d472 |
2 changed files with 203 additions and 70 deletions
|
@ -9,7 +9,7 @@
|
||||||
pkg-config
|
pkg-config
|
||||||
openssl
|
openssl
|
||||||
|
|
||||||
watchexec
|
bacon
|
||||||
|
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
libGL
|
libGL
|
||||||
|
|
271
src/main.rs
271
src/main.rs
|
@ -1,10 +1,11 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use iced::theme::Theme;
|
use iced::theme::Theme;
|
||||||
use iced::widget::{column, container, scrollable, text, text_input};
|
use iced::widget::{column, container, mouse_area, row, scrollable, text, text_input};
|
||||||
use iced::{executor, Application, Color, Length, Settings};
|
use iced::{executor, Application, Background, Color, Length, Settings};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
||||||
|
@ -43,7 +44,12 @@ async fn main() -> Result<()> {
|
||||||
let config = irc::client::prelude::Config {
|
let config = irc::client::prelude::Config {
|
||||||
nickname: Some("cri".to_string()),
|
nickname: Some("cri".to_string()),
|
||||||
server: Some("vijf.life".to_string()),
|
server: Some("vijf.life".to_string()),
|
||||||
channels: vec!["#h".to_string()],
|
channels: vec![
|
||||||
|
"#h".to_string(),
|
||||||
|
"#test".to_string(),
|
||||||
|
"#test1".to_string(),
|
||||||
|
"#test2".to_string(),
|
||||||
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,10 +66,35 @@ async fn main() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum IrcMessage {
|
||||||
|
Join {
|
||||||
|
nickname: String,
|
||||||
|
},
|
||||||
|
Part {
|
||||||
|
nickname: String,
|
||||||
|
reason: Option<String>,
|
||||||
|
},
|
||||||
|
Nick {
|
||||||
|
old: String,
|
||||||
|
new: String,
|
||||||
|
},
|
||||||
|
Quit {
|
||||||
|
nickname: String,
|
||||||
|
reason: Option<String>,
|
||||||
|
},
|
||||||
|
Privmsg {
|
||||||
|
nickname: String,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
struct Cri {
|
struct Cri {
|
||||||
message_rx: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
message_rx: RefCell<Option<UnboundedReceiver<irc::proto::Message>>>,
|
||||||
message_log: Vec<irc::proto::Message>,
|
|
||||||
input_tx: RefCell<UnboundedSender<irc::proto::Message>>,
|
input_tx: RefCell<UnboundedSender<irc::proto::Message>>,
|
||||||
|
|
||||||
|
active_channel: Option<String>,
|
||||||
|
|
||||||
|
message_log: HashMap<Option<String>, Vec<IrcMessage>>,
|
||||||
input_value: String,
|
input_value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,24 +104,31 @@ struct CriFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum UiMessage {
|
||||||
IrcMessageReceived(Box<irc::proto::Message>),
|
IrcMessageReceived(Box<irc::proto::Message>),
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
InputSubmitted,
|
InputSubmitted,
|
||||||
|
HandleChannelPress(Option<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Application for Cri {
|
impl Application for Cri {
|
||||||
type Executor = executor::Default;
|
type Executor = executor::Default;
|
||||||
type Message = Message;
|
type Message = UiMessage;
|
||||||
type Theme = Theme;
|
type Theme = Theme;
|
||||||
type Flags = CriFlags;
|
type Flags = CriFlags;
|
||||||
|
|
||||||
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
fn new(flags: Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||||
|
let mut message_log = HashMap::new();
|
||||||
|
message_log.insert(None, Vec::new());
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
message_rx: RefCell::new(Some(flags.message_rx)),
|
message_rx: RefCell::new(Some(flags.message_rx)),
|
||||||
message_log: Vec::new(),
|
|
||||||
input_tx: RefCell::new(flags.input_tx),
|
input_tx: RefCell::new(flags.input_tx),
|
||||||
|
|
||||||
|
active_channel: None,
|
||||||
|
|
||||||
|
message_log,
|
||||||
input_value: String::new(),
|
input_value: String::new(),
|
||||||
},
|
},
|
||||||
iced::Command::none(),
|
iced::Command::none(),
|
||||||
|
@ -103,18 +141,98 @@ impl Application for Cri {
|
||||||
|
|
||||||
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
fn update(&mut self, message: Self::Message) -> iced::Command<Self::Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::IrcMessageReceived(message) => self.message_log.push(*message),
|
UiMessage::IrcMessageReceived(message) => {
|
||||||
Message::InputChanged(text) => self.input_value = text,
|
let message = *message;
|
||||||
Message::InputSubmitted => {
|
|
||||||
let command =
|
|
||||||
irc::proto::Command::PRIVMSG("#h".to_string(), self.input_value.clone());
|
|
||||||
let message: irc::proto::Message = command.into();
|
|
||||||
|
|
||||||
self.message_log.push(message.clone());
|
// TODO use actual nickname
|
||||||
self.input_tx.borrow().send(message.clone()).unwrap();
|
let source_nickname = message.source_nickname().unwrap_or("cri").to_string();
|
||||||
|
|
||||||
|
match &message.command {
|
||||||
|
irc::proto::Command::JOIN(chanlist, _, _) => {
|
||||||
|
self.message_log
|
||||||
|
.entry(Some(chanlist.to_string()))
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(IrcMessage::Join {
|
||||||
|
nickname: source_nickname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
irc::proto::Command::PART(chanlist, comment) => {
|
||||||
|
self.message_log
|
||||||
|
.entry(Some(chanlist.to_string()))
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(IrcMessage::Part {
|
||||||
|
nickname: source_nickname,
|
||||||
|
reason: comment.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
irc::proto::Command::NICK(new) => {
|
||||||
|
let channels = self.message_log.keys().cloned().collect::<Vec<_>>();
|
||||||
|
for channel in channels {
|
||||||
|
if channel.is_some() {
|
||||||
|
self.message_log
|
||||||
|
.get_mut(&channel)
|
||||||
|
.unwrap()
|
||||||
|
.push(IrcMessage::Nick {
|
||||||
|
old: source_nickname.clone(),
|
||||||
|
new: new.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irc::proto::Command::QUIT(comment) => {
|
||||||
|
let channels = self.message_log.keys().cloned().collect::<Vec<_>>();
|
||||||
|
for channel in channels {
|
||||||
|
if channel.is_some() {
|
||||||
|
self.message_log
|
||||||
|
.get_mut(&channel)
|
||||||
|
.unwrap()
|
||||||
|
.push(IrcMessage::Quit {
|
||||||
|
nickname: source_nickname.clone(),
|
||||||
|
reason: comment.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
irc::proto::Command::PRIVMSG(msgtarget, content) => {
|
||||||
|
let channel = message.response_target().unwrap_or(msgtarget);
|
||||||
|
|
||||||
|
self.message_log
|
||||||
|
.entry(Some(channel.to_string()))
|
||||||
|
.or_insert_with(Vec::new)
|
||||||
|
.push(IrcMessage::Privmsg {
|
||||||
|
nickname: source_nickname,
|
||||||
|
message: content.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UiMessage::InputChanged(text) => self.input_value = text,
|
||||||
|
UiMessage::InputSubmitted => {
|
||||||
|
if let Some(active_channel) = &self.active_channel {
|
||||||
|
let command = irc::proto::Command::PRIVMSG(
|
||||||
|
active_channel.to_string(),
|
||||||
|
self.input_value.clone(),
|
||||||
|
);
|
||||||
|
let message: irc::proto::Message = command.into();
|
||||||
|
|
||||||
|
self.message_log
|
||||||
|
.get_mut(&self.active_channel)
|
||||||
|
.unwrap()
|
||||||
|
.push(IrcMessage::Privmsg {
|
||||||
|
nickname: String::from("cri"),
|
||||||
|
message: self.input_value.clone(),
|
||||||
|
});
|
||||||
|
self.input_tx.borrow().send(message.clone()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
self.input_value.clear();
|
self.input_value.clear();
|
||||||
}
|
}
|
||||||
|
UiMessage::HandleChannelPress(channel) => self.active_channel = channel,
|
||||||
}
|
}
|
||||||
iced::Command::none()
|
iced::Command::none()
|
||||||
}
|
}
|
||||||
|
@ -125,7 +243,7 @@ impl Application for Cri {
|
||||||
self.message_rx.take(),
|
self.message_rx.take(),
|
||||||
move |mut receiver| async move {
|
move |mut receiver| async move {
|
||||||
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
let message = receiver.as_mut().unwrap().recv().await.unwrap();
|
||||||
(Message::IrcMessageReceived(Box::new(message)), receiver)
|
(UiMessage::IrcMessageReceived(Box::new(message)), receiver)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -133,61 +251,41 @@ impl Application for Cri {
|
||||||
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
fn view(&self) -> iced::Element<'_, Self::Message, iced::Renderer<Self::Theme>> {
|
||||||
let dark_green = Color::new(0.153, 0.682, 0.377, 1.0);
|
let dark_green = Color::new(0.153, 0.682, 0.377, 1.0);
|
||||||
let dark_grey = Color::new(0.584, 0.647, 0.651, 1.0);
|
let dark_grey = Color::new(0.584, 0.647, 0.651, 1.0);
|
||||||
let darker_grey = Color::new(0.498, 0.549, 0.553, 1.0);
|
let _darker_grey = Color::new(0.498, 0.549, 0.553, 1.0);
|
||||||
|
|
||||||
let log = scrollable(column(
|
let log = scrollable(column(
|
||||||
self.message_log
|
self.message_log[&self.active_channel]
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|message| {
|
.flat_map(|message| {
|
||||||
// TODO use actual nickname
|
match message {
|
||||||
let source_nickname = message.source_nickname().unwrap_or("cri");
|
IrcMessage::Join { nickname } => {
|
||||||
|
Some(text(format!("* {nickname} joined the channel")).style(dark_green))
|
||||||
|
}
|
||||||
|
IrcMessage::Part { nickname, reason } => {
|
||||||
|
let reason = match reason {
|
||||||
|
Some(reason) => format!(" ({reason})"),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
Some(
|
||||||
|
text(format!("* {nickname} left the channel{reason}"))
|
||||||
|
.style(dark_green),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IrcMessage::Nick { old, new } => {
|
||||||
|
Some(text(format!("* {old} changed their nickname to {new}")).style(dark_green))
|
||||||
|
}
|
||||||
|
IrcMessage::Quit { nickname, reason }=> {
|
||||||
|
let reason = match reason {
|
||||||
|
Some(reason) => format!(" ({reason})"),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
match &message.command {
|
Some(text(format!("* {nickname} quit the server{reason}"))
|
||||||
irc::proto::Command::NICK(nickname) => Some(
|
.style(dark_green))
|
||||||
text(format!(
|
},
|
||||||
"* {} changed their nickname to {}",
|
IrcMessage::Privmsg { nickname, message } => {
|
||||||
source_nickname, nickname,
|
Some(text(format!("<{nickname}> {message}")))
|
||||||
))
|
}
|
||||||
.style(dark_green),
|
|
||||||
),
|
|
||||||
irc::proto::Command::JOIN(chanlist, _, _) => Some(
|
|
||||||
text(format!(
|
|
||||||
"[{}] * {} joined the channel",
|
|
||||||
chanlist, source_nickname,
|
|
||||||
))
|
|
||||||
.style(dark_green),
|
|
||||||
),
|
|
||||||
irc::proto::Command::PART(chanlist, comment) => Some(
|
|
||||||
text(format!(
|
|
||||||
"[{}] * {} left the channel{}",
|
|
||||||
chanlist,
|
|
||||||
source_nickname,
|
|
||||||
comment
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| format!(" ({})", c))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
))
|
|
||||||
.style(dark_green),
|
|
||||||
),
|
|
||||||
irc::proto::Command::QUIT(comment) => Some(
|
|
||||||
text(format!(
|
|
||||||
"* {} quit the server{}",
|
|
||||||
source_nickname,
|
|
||||||
comment
|
|
||||||
.as_ref()
|
|
||||||
.map(|c| format!(" ({})", c))
|
|
||||||
.unwrap_or_default(),
|
|
||||||
))
|
|
||||||
.style(dark_green),
|
|
||||||
),
|
|
||||||
irc::proto::Command::PRIVMSG(msgtarget, content) => Some(text(format!(
|
|
||||||
"[{}] <{}> {}",
|
|
||||||
message.response_target().unwrap_or(msgtarget),
|
|
||||||
source_nickname,
|
|
||||||
content,
|
|
||||||
))),
|
|
||||||
_ => None,
|
|
||||||
//_ => text(format!("{:?}", m.command)).style(dark_grey),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(|element| element.into())
|
.map(|element| element.into())
|
||||||
|
@ -198,10 +296,45 @@ impl Application for Cri {
|
||||||
|
|
||||||
let message_box = text_input("your magnum opus", &self.input_value)
|
let message_box = text_input("your magnum opus", &self.input_value)
|
||||||
.id(INPUT_ID.clone())
|
.id(INPUT_ID.clone())
|
||||||
.on_input(Message::InputChanged)
|
.on_input(UiMessage::InputChanged)
|
||||||
.on_submit(Message::InputSubmitted);
|
.on_submit(UiMessage::InputSubmitted);
|
||||||
|
|
||||||
let content = column![log, message_box].height(Length::Fill);
|
let channels = column(
|
||||||
|
self.message_log
|
||||||
|
.keys()
|
||||||
|
.map(|channel| {
|
||||||
|
let channel_name = channel
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&String::from("Server"))
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let text = text(channel_name);
|
||||||
|
let is_active = &self.active_channel == channel;
|
||||||
|
let container = container(text)
|
||||||
|
.style(move |_: &_| {
|
||||||
|
let background = if is_active {
|
||||||
|
Some(Background::Color(dark_grey))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
container::Appearance {
|
||||||
|
background,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.width(Length::Fill);
|
||||||
|
|
||||||
|
mouse_area(container)
|
||||||
|
.on_press(UiMessage::HandleChannelPress(channel.clone()))
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.width(Length::Fixed(100.0));
|
||||||
|
|
||||||
|
let content = row![channels, column![log, message_box].height(Length::Fill)];
|
||||||
|
|
||||||
container(content).into()
|
container(content).into()
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue