Style the chat list, small refactor
This commit is contained in:
parent
d077040823
commit
878e805b53
2 changed files with 197 additions and 166 deletions
211
src/main.rs
211
src/main.rs
|
@ -1,19 +1,21 @@
|
||||||
mod irc_message;
|
mod irc_message;
|
||||||
mod message_log;
|
mod message_log;
|
||||||
|
|
||||||
use crate::irc_message::IrcMessage;
|
use crate::{irc_message::IrcMessage, message_log::MessageLog};
|
||||||
use crate::message_log::MessageLog;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use iced::alignment::Horizontal;
|
use iced::{
|
||||||
use iced::theme::Theme;
|
executor,
|
||||||
use iced::widget::{column, container, mouse_area, row, scrollable, text, text_input};
|
theme::Theme,
|
||||||
use iced::{executor, Application, Background, Color, Length, Settings};
|
widget::{column, container, mouse_area, row, text, text_input},
|
||||||
|
Application, Background, Color, Length, Settings,
|
||||||
|
};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tokio::select;
|
use std::cell::RefCell;
|
||||||
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
|
use tokio::{
|
||||||
|
select,
|
||||||
|
sync::mpsc::{self, UnboundedReceiver, UnboundedSender},
|
||||||
|
};
|
||||||
|
|
||||||
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
static INPUT_ID: Lazy<text_input::Id> = Lazy::new(text_input::Id::unique);
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ struct CriFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum UiMessage {
|
pub enum UiMessage {
|
||||||
IrcMessageReceived(Box<irc::proto::Message>),
|
IrcMessageReceived(Box<irc::proto::Message>),
|
||||||
InputChanged(String),
|
InputChanged(String),
|
||||||
InputSubmitted,
|
InputSubmitted,
|
||||||
|
@ -195,142 +197,10 @@ 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 lighter_grey = Color::new(0.93, 0.94, 0.95, 1.0);
|
|
||||||
let dark_grey = Color::new(0.58, 0.65, 0.65, 1.0);
|
let dark_grey = Color::new(0.58, 0.65, 0.65, 1.0);
|
||||||
let lighter_green = Color::new(0.94, 0.99, 0.87, 1.0);
|
let light_blue = Color::new(0.26, 0.62, 0.85, 1.0);
|
||||||
let dark_red = Color::new(0.75, 0.22, 0.17, 1.0);
|
|
||||||
|
|
||||||
let event_appearance = container::Appearance {
|
let log = self.message_log.view(&self.active_channel);
|
||||||
background: Some(Background::Color(dark_grey)),
|
|
||||||
border_radius: 8.0.into(),
|
|
||||||
text_color: Some(Color::WHITE),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let message_appearance = container::Appearance {
|
|
||||||
background: Some(Background::Color(Color::WHITE)),
|
|
||||||
border_radius: 8.0.into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let own_message_appearance = container::Appearance {
|
|
||||||
background: Some(Background::Color(lighter_green)),
|
|
||||||
border_radius: 8.0.into(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let messages = self
|
|
||||||
.message_log
|
|
||||||
.get(&self.active_channel)
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.flat_map(|message| -> Option<iced::Element<_>> {
|
|
||||||
match message {
|
|
||||||
IrcMessage::Join { nickname } => Some(
|
|
||||||
container(
|
|
||||||
container(text(format!("{nickname} joined the channel")))
|
|
||||||
.style(move |_: &_| event_appearance)
|
|
||||||
.padding([3, 10]),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.padding([3, 0])
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
IrcMessage::Part { nickname, reason } => {
|
|
||||||
let reason = match reason {
|
|
||||||
Some(reason) => format!(" ({reason})"),
|
|
||||||
None => String::new(),
|
|
||||||
};
|
|
||||||
Some(
|
|
||||||
container(
|
|
||||||
container(text(format!("{nickname} left the channel{reason}")))
|
|
||||||
.style(move |_: &_| event_appearance)
|
|
||||||
.padding([3, 10]),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.padding([3, 0])
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IrcMessage::Nick { old, new } => Some(
|
|
||||||
container(
|
|
||||||
container(text(format!("{old} changed their nickname to {new}")))
|
|
||||||
.style(move |_: &_| event_appearance)
|
|
||||||
.padding([3, 10]),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.padding([3, 0])
|
|
||||||
.into(),
|
|
||||||
),
|
|
||||||
IrcMessage::Quit { nickname, reason } => {
|
|
||||||
let reason = match reason {
|
|
||||||
Some(reason) => format!(" ({reason})"),
|
|
||||||
None => String::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(
|
|
||||||
container(
|
|
||||||
container(text(format!("{nickname} left the server{reason}")))
|
|
||||||
.style(move |_: &_| event_appearance)
|
|
||||||
.padding([3, 10]),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.center_x()
|
|
||||||
.padding([3, 0])
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IrcMessage::Privmsg { nickname, message } => {
|
|
||||||
// TODO don't hardcode nickname lol
|
|
||||||
let is_self = nickname == "cri";
|
|
||||||
|
|
||||||
let mut elements = Vec::new();
|
|
||||||
if !is_self {
|
|
||||||
elements.push(text(nickname).style(dark_red).into())
|
|
||||||
}
|
|
||||||
elements.push(text(message).into());
|
|
||||||
|
|
||||||
Some(
|
|
||||||
container(
|
|
||||||
container(column(elements))
|
|
||||||
.style(move |_: &_| {
|
|
||||||
if is_self {
|
|
||||||
own_message_appearance
|
|
||||||
} else {
|
|
||||||
message_appearance
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.padding([4, 10]),
|
|
||||||
)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.align_x(if is_self {
|
|
||||||
Horizontal::Right
|
|
||||||
} else {
|
|
||||||
Horizontal::Left
|
|
||||||
})
|
|
||||||
.padding([4, 8])
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.map(|element| element.into())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let log = container(
|
|
||||||
scrollable(column(messages))
|
|
||||||
.height(Length::Fill)
|
|
||||||
.width(Length::Fill),
|
|
||||||
)
|
|
||||||
.height(Length::Fill)
|
|
||||||
.width(Length::Fill)
|
|
||||||
.style(move |_: &_| container::Appearance {
|
|
||||||
background: Some(Background::Color(lighter_grey)),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
|
|
||||||
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())
|
||||||
|
@ -339,34 +209,51 @@ impl Application for Cri {
|
||||||
|
|
||||||
let channels = column(
|
let channels = column(
|
||||||
self.message_log
|
self.message_log
|
||||||
.get_channels()
|
.get_all()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|channel| {
|
.map(|(&ref channel, log)| {
|
||||||
let text = text(channel);
|
let is_active = &self.active_channel == channel;
|
||||||
let is_active = self.active_channel.as_ref() == Some(channel);
|
let channel_name = match channel {
|
||||||
let container = container(text)
|
None => "Server",
|
||||||
.style(move |_: &_| {
|
Some(channel) => channel,
|
||||||
let background = if is_active {
|
};
|
||||||
Some(Background::Color(dark_grey))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
container::Appearance {
|
let text_color = if is_active { Some(Color::WHITE) } else { None };
|
||||||
background,
|
let nickname_color = if is_active { Color::WHITE } else { light_blue };
|
||||||
..Default::default()
|
let no_message_color = if is_active { Color::WHITE } else { dark_grey };
|
||||||
}
|
|
||||||
|
let last_message = log
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find_map(|m| match m {
|
||||||
|
IrcMessage::Privmsg { nickname, message } => Some(row![
|
||||||
|
text(format!("{nickname}: ")).style(nickname_color),
|
||||||
|
text(message)
|
||||||
|
]),
|
||||||
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.unwrap_or(row![text("No messages").style(no_message_color)]);
|
||||||
|
|
||||||
|
let container = container(column![text(channel_name), last_message])
|
||||||
|
.style(move |_: &_| container::Appearance {
|
||||||
|
background: match is_active {
|
||||||
|
true => Some(Background::Color(light_blue)),
|
||||||
|
false => None,
|
||||||
|
},
|
||||||
|
text_color,
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.padding([4, 4])
|
||||||
.width(Length::Fill);
|
.width(Length::Fill);
|
||||||
|
|
||||||
mouse_area(container)
|
mouse_area(container)
|
||||||
.on_press(UiMessage::HandleChannelPress(Some(channel.to_string())))
|
.on_press(UiMessage::HandleChannelPress(channel.clone()))
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
.height(Length::Fill)
|
.height(Length::Fill)
|
||||||
.width(Length::Fixed(100.0));
|
.width(Length::Fixed(200.0));
|
||||||
|
|
||||||
let content = row![channels, column![log, message_box].height(Length::Fill)];
|
let content = row![channels, column![log, message_box].height(Length::Fill)];
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
use crate::irc_message::IrcMessage;
|
use crate::irc_message::IrcMessage;
|
||||||
|
|
||||||
|
use iced::{
|
||||||
|
alignment::Horizontal,
|
||||||
|
widget::{column, container, scrollable, text, Container},
|
||||||
|
Background, Color, Length,
|
||||||
|
};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub struct MessageLog(HashMap<Option<String>, Vec<IrcMessage>>);
|
pub struct MessageLog(HashMap<Option<String>, Vec<IrcMessage>>);
|
||||||
|
@ -10,10 +16,10 @@ impl<'a> MessageLog {
|
||||||
Self(log)
|
Self(log)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_channels(&'a self) -> Vec<&String> {
|
pub fn get_all(&'a self) -> Vec<(&'a Option<String>, &'a Vec<IrcMessage>)> {
|
||||||
let mut keys = self.0.keys().flatten().collect::<Vec<_>>();
|
let mut log: Vec<(&Option<String>, &Vec<IrcMessage>)> = self.0.iter().collect();
|
||||||
keys.sort_unstable();
|
log.sort_unstable_by_key(|(name, _)| name.as_deref());
|
||||||
keys
|
log
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, channel: &Option<String>) -> Option<&Vec<IrcMessage>> {
|
pub fn get(&self, channel: &Option<String>) -> Option<&Vec<IrcMessage>> {
|
||||||
|
@ -61,4 +67,142 @@ impl<'a> MessageLog {
|
||||||
message: message.to_string(),
|
message: message.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn view(&self, active_channel: &Option<String>) -> Container<'_, crate::UiMessage> {
|
||||||
|
let lighter_grey = Color::new(0.93, 0.94, 0.95, 1.0);
|
||||||
|
let dark_grey = Color::new(0.58, 0.65, 0.65, 1.0);
|
||||||
|
let lighter_green = Color::new(0.94, 0.99, 0.87, 1.0);
|
||||||
|
let dark_red = Color::new(0.75, 0.22, 0.17, 1.0);
|
||||||
|
|
||||||
|
let event_appearance = container::Appearance {
|
||||||
|
background: Some(Background::Color(dark_grey)),
|
||||||
|
border_radius: 8.0.into(),
|
||||||
|
text_color: Some(Color::WHITE),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let message_appearance = container::Appearance {
|
||||||
|
background: Some(Background::Color(Color::WHITE)),
|
||||||
|
border_radius: 8.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let own_message_appearance = container::Appearance {
|
||||||
|
background: Some(Background::Color(lighter_green)),
|
||||||
|
border_radius: 8.0.into(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let messages = self
|
||||||
|
.get(active_channel)
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|message| -> Option<iced::Element<_>> {
|
||||||
|
match message {
|
||||||
|
IrcMessage::Join { nickname } => Some(
|
||||||
|
container(
|
||||||
|
container(text(format!("{nickname} joined the channel")))
|
||||||
|
.style(move |_: &_| event_appearance)
|
||||||
|
.padding([3, 10]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.padding([3, 0])
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
IrcMessage::Part { nickname, reason } => {
|
||||||
|
let reason = match reason {
|
||||||
|
Some(reason) => format!(" ({reason})"),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
Some(
|
||||||
|
container(
|
||||||
|
container(text(format!("{nickname} left the channel{reason}")))
|
||||||
|
.style(move |_: &_| event_appearance)
|
||||||
|
.padding([3, 10]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.padding([3, 0])
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IrcMessage::Nick { old, new } => Some(
|
||||||
|
container(
|
||||||
|
container(text(format!("{old} changed their nickname to {new}")))
|
||||||
|
.style(move |_: &_| event_appearance)
|
||||||
|
.padding([3, 10]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.padding([3, 0])
|
||||||
|
.into(),
|
||||||
|
),
|
||||||
|
IrcMessage::Quit { nickname, reason } => {
|
||||||
|
let reason = match reason {
|
||||||
|
Some(reason) => format!(" ({reason})"),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(
|
||||||
|
container(
|
||||||
|
container(text(format!("{nickname} left the server{reason}")))
|
||||||
|
.style(move |_: &_| event_appearance)
|
||||||
|
.padding([3, 10]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.center_x()
|
||||||
|
.padding([3, 0])
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IrcMessage::Privmsg { nickname, message } => {
|
||||||
|
// TODO don't hardcode nickname lol
|
||||||
|
let is_self = nickname == "cri";
|
||||||
|
|
||||||
|
let mut elements = Vec::new();
|
||||||
|
if !is_self {
|
||||||
|
elements.push(text(nickname).style(dark_red).into())
|
||||||
|
}
|
||||||
|
elements.push(text(message).into());
|
||||||
|
|
||||||
|
Some(
|
||||||
|
container(
|
||||||
|
container(column(elements))
|
||||||
|
.style(move |_: &_| {
|
||||||
|
if is_self {
|
||||||
|
own_message_appearance
|
||||||
|
} else {
|
||||||
|
message_appearance
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.padding([4, 10]),
|
||||||
|
)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.align_x(if is_self {
|
||||||
|
Horizontal::Right
|
||||||
|
} else {
|
||||||
|
Horizontal::Left
|
||||||
|
})
|
||||||
|
.padding([4, 8])
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|element| element.into())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
container(
|
||||||
|
scrollable(column(messages))
|
||||||
|
.height(Length::Fill)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.height(Length::Fill)
|
||||||
|
.width(Length::Fill)
|
||||||
|
.style(move |_: &_| container::Appearance {
|
||||||
|
background: Some(Background::Color(lighter_grey)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue