Style the chat list, small refactor

This commit is contained in:
Sijmen 2023-11-10 01:56:32 +01:00
parent d077040823
commit 878e805b53
2 changed files with 197 additions and 166 deletions

View file

@ -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)];

View file

@ -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()
})
}
} }