diff options
Diffstat (limited to '')
-rw-r--r-- | src/icons.rs | 93 | ||||
-rw-r--r-- | src/main.rs | 8 | ||||
-rw-r--r-- | src/message_view.rs | 194 |
3 files changed, 229 insertions, 66 deletions
diff --git a/src/icons.rs b/src/icons.rs index 934a0c8..63e7708 100644 --- a/src/icons.rs +++ b/src/icons.rs @@ -1,6 +1,7 @@ +use filamento::chat::Delivery; use iced::widget::svg; use iced::widget::{svg::Handle, Svg}; -use iced::Element; +use iced::{color, Element, Theme}; pub enum Icon { AddContact24, @@ -23,6 +24,12 @@ pub enum Icon { Sent16, } +impl Icon { + pub fn svg(self) -> Svg<'static> { + self.into() + } +} + impl From<Icon> for Svg<'_> { fn from(value: Icon) -> Self { match value { @@ -30,17 +37,26 @@ impl From<Icon> for Svg<'_> { "../assets/icons/addcontact24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Attachment24 => svg(Handle::from_memory(include_bytes!( "../assets/icons/attachment24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Away16 => svg(Handle::from_memory(include_bytes!( "../assets/icons/away16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Away16Color => svg(Handle::from_memory(include_bytes!( "../assets/icons/away16color.svg" ))) @@ -50,7 +66,10 @@ impl From<Icon> for Svg<'_> { "../assets/icons/bubble16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Bubble16Color => svg(Handle::from_memory(include_bytes!( "../assets/icons/bubble16color.svg" ))) @@ -60,22 +79,34 @@ impl From<Icon> for Svg<'_> { "../assets/icons/bubble24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Contact24 => svg(Handle::from_memory(include_bytes!( "../assets/icons/contact24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Delivered16 => svg(Handle::from_memory(include_bytes!( "../assets/icons/delivered16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Dnd16 => svg(Handle::from_memory(include_bytes!( "../assets/icons/dnd16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Dnd16Color => svg(Handle::from_memory(include_bytes!( "../assets/icons/dnd16color.svg" ))) @@ -90,32 +121,50 @@ impl From<Icon> for Svg<'_> { "../assets/icons/forward24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Heart24 => svg(Handle::from_memory(include_bytes!( "../assets/icons/heart24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::NewBubble24 => svg(Handle::from_memory(include_bytes!( "../assets/icons/newbubble24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Reply24 => svg(Handle::from_memory(include_bytes!( "../assets/icons/reply24.svg" ))) .width(24) - .height(24), + .height(24) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Sending16 => svg(Handle::from_memory(include_bytes!( "../assets/icons/sending16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), Icon::Sent16 => svg(Handle::from_memory(include_bytes!( "../assets/icons/sent16.svg" ))) .width(16) - .height(16), + .height(16) + .style(|theme: &Theme, _status| svg::Style { + color: Some(theme.extended_palette().background.base.text), + }), } } } @@ -125,3 +174,17 @@ impl<Message> From<Icon> for Element<'_, Message> { Into::<Svg>::into(value).into() } } + +pub fn delivery_to_icon_svg(delivery: Delivery) -> Option<Svg<'static>> { + match delivery { + Delivery::Sending => Some(Icon::Sending16.into()), + Delivery::Written => None, + Delivery::Sent => Some(Icon::Sent16.into()), + Delivery::Delivered => Some(Icon::Delivered16.into()), + Delivery::Read => Some(Icon::Delivered16.svg().style(|_theme, _| svg::Style { + color: Some(color!(0x52cf6e)), + })), + Delivery::Failed => Some(Icon::Error16Color.into()), + Delivery::Queued => Some(Icon::Sending16.into()), + } +} diff --git a/src/main.rs b/src/main.rs index b785562..8b50bef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -527,7 +527,7 @@ async fn main() -> iced::Result { } }; let mut font = Font::with_name("K2D"); - font.weight = Weight::Medium; + font.weight = Weight::Light; // font.stretch = Stretch::Condensed; iced::application("Macaw", Macaw::update, Macaw::view) .font(include_bytes!("../assets/fonts/Diolce-Regular.ttf")) @@ -813,7 +813,7 @@ impl Macaw { } } if let Some(open_chat) = &mut self.open_chat { - if let Some(message) = open_chat.messages.get_mut(&id) { + if let Some((message, _)) = open_chat.messages.get_mut(&id) { message.delivery = Some(delivery) } } @@ -828,7 +828,7 @@ impl Macaw { chats_list_item.user.nick = nick.clone() } if let Some(open_chat) = &mut self.open_chat { - for (_, message) in &mut open_chat.messages { + for (_, (message, _)) in &mut open_chat.messages { if message.from.jid == jid { message.from.nick = nick.clone() } @@ -849,7 +849,7 @@ impl Macaw { } if let Some(open_chat) = &mut self.open_chat { // TODO: consider using an indexmap with two keys for speeding this up? - for (_, message) in &mut open_chat.messages { + for (_, (message, _)) in &mut open_chat.messages { if message.from.jid == jid { message.from.avatar = id.clone() } diff --git a/src/message_view.rs b/src/message_view.rs index f5319bd..742aac0 100644 --- a/src/message_view.rs +++ b/src/message_view.rs @@ -1,8 +1,10 @@ use std::{path::PathBuf, time::Duration}; use chrono::{NaiveDate, NaiveDateTime, TimeDelta}; -use iced::color; -use iced::widget::text_editor; +use filamento::chat::Delivery; +use iced::advanced::Overlay; +use iced::alignment::Vertical; +use iced::widget::{horizontal_space, mouse_area, text_editor, vertical_space, Container}; use iced::{ border::Radius, font::{Style, Weight}, @@ -11,11 +13,13 @@ use iced::{ Length::{Fill, Shrink}, Theme, }; +use iced::{color, overlay, Length, Padding}; use indexmap::IndexMap; use jid::JID; use serde::{Deserialize, Serialize}; use uuid::Uuid; +use crate::icons; use crate::{icons::Icon, MacawChat, MacawMessage}; pub struct MessageView { @@ -23,7 +27,7 @@ pub struct MessageView { // references chats, users pub chat: MacawChat, // references users, messages - pub messages: IndexMap<Uuid, MacawMessage>, + pub messages: IndexMap<Uuid, (MacawMessage, bool)>, pub config: Config, pub new_message: Content, pub shift_pressed: bool, @@ -46,6 +50,9 @@ impl Default for Config { pub enum Message { MessageHistory(Vec<MacawMessage>), Message(MacawMessage), + MessageHovered(Uuid), + MessageUnhovered(Uuid), + MessageRightClicked(Uuid), MessageCompose(text_editor::Action), SendMessage(String), } @@ -105,61 +112,80 @@ impl MessageView { if self.messages.is_empty() { self.messages = macaw_messages .into_iter() - .map(|message| (message.id, message)) + .map(|message| (message.id, (message, false))) .collect() } else { for message in macaw_messages { let index = match self .messages - .binary_search_by(|_, value| value.timestamp.cmp(&message.timestamp)) + .binary_search_by(|_, value| value.0.timestamp.cmp(&message.timestamp)) { Ok(i) => i, Err(i) => i, }; - self.messages.insert_before(index, message.id, message); + self.messages + .insert_before(index, message.id, (message, false)); } } Action::None } Message::Message(macaw_message) => { if let Some((_, last)) = self.messages.last() { - if last.timestamp < macaw_message.timestamp { - self.messages.insert(macaw_message.id, macaw_message); + if last.0.timestamp < macaw_message.timestamp { + self.messages + .insert(macaw_message.id, (macaw_message, false)); } else { let index = match self.messages.binary_search_by(|_, value| { - value.timestamp.cmp(&macaw_message.timestamp) + value.0.timestamp.cmp(&macaw_message.timestamp) }) { Ok(i) => i, Err(i) => i, }; - self.messages - .insert_before(index, macaw_message.id, macaw_message); + self.messages.insert_before( + index, + macaw_message.id, + (macaw_message, false), + ); } } else { - self.messages.insert(macaw_message.id, macaw_message); + self.messages + .insert(macaw_message.id, (macaw_message, false)); + } + Action::None + } + Message::MessageHovered(uuid) => { + if let Some(message) = self.messages.get_mut(&uuid) { + message.1 = true; } Action::None } + Message::MessageUnhovered(uuid) => { + if let Some(message) = self.messages.get_mut(&uuid) { + message.1 = false; + } + Action::None + } + Message::MessageRightClicked(uuid) => todo!(), } } pub fn view(&self) -> Element<Message> { - let mut messages_view = column![].spacing(8).padding(8); + let mut messages_view = column![]; let mut last_timestamp = NaiveDateTime::MIN; let mut last_user: Option<JID> = None; for (_id, message) in &self.messages { - let message_timestamp = message.timestamp.naive_local(); + let message_timestamp = message.0.timestamp.naive_local(); if message_timestamp.date() > last_timestamp.date() { messages_view = messages_view.push(date(message_timestamp.date())); } - if last_user.as_ref() != Some(&message.from.jid) + if last_user.as_ref() != Some(&message.0.from.jid) || message_timestamp - last_timestamp > TimeDelta::minutes(3) { - messages_view = messages_view.push(self.message(message, true)); + messages_view = messages_view.push(self.message(&message.0, message.1, true)); } else { - messages_view = messages_view.push(self.message(message, false)); + messages_view = messages_view.push(self.message(&message.0, message.1, false)); } - last_user = Some(message.from.jid.clone()); + last_user = Some(message.0.from.jid.clone()); last_timestamp = message_timestamp; } let text_editor = text_editor(&self.new_message) @@ -227,11 +253,17 @@ impl MessageView { .into() } - pub fn message<'a>(&'a self, message: &'a MacawMessage, major: bool) -> Element<'a, Message> { + pub fn message<'a>( + &'a self, + message: &'a MacawMessage, + hovered: bool, + major: bool, + // next_read: bool, + ) -> Element<'a, Message> { let timestamp = message.timestamp.naive_local(); let timestamp = timestamp.time().format("%H:%M").to_string(); - if major { + let container: Container<Message> = if major { let nick: String = if let Some(nick) = &message.from.nick { nick.to_string() } else { @@ -239,39 +271,107 @@ impl MessageView { }; let mut bold = Font::with_name("K2D"); bold.weight = Weight::Bold; - let mut header = row![text(nick).font(bold), text(timestamp)].spacing(8); - if let Some(delivery) = message.delivery { - let icon = match delivery { - filamento::chat::Delivery::Sending => Some(Icon::Sending16), - filamento::chat::Delivery::Written => None, - filamento::chat::Delivery::Sent => Some(Icon::Sent16), - filamento::chat::Delivery::Delivered => Some(Icon::Delivered16), - filamento::chat::Delivery::Read => Some(Icon::Delivered16), - filamento::chat::Delivery::Failed => Some(Icon::Error16Color), - filamento::chat::Delivery::Queued => Some(Icon::Sending16), - }; - if let Some(icon) = icon { - header = header.push(icon); - } - } + let mut thin = Font::with_name("K2D"); + thin.weight = Weight::Thin; + let timestamp = text(timestamp).size(12).font(thin); + let header = row![text(nick).font(bold), timestamp] + .align_y(Vertical::Bottom) + .spacing(8); let message_right = column![header, text(&message.body.body)].spacing(8); - let mut major_message = row([]); - if let Some(avatar) = &message.from.avatar { + let avatar = if let Some(avatar) = &message.from.avatar { let mut path = self.file_root.join(avatar); path.set_extension("jpg"); // info!("got avatar: {:?}", path); - major_message = major_message.push(container(image(path).width(48).height(48))); + container(image(path).width(48).height(48)) + } else { + container("").width(48) + }; + let show_delivery = match message.delivery { + Some(Delivery::Sending) => true, + Some(Delivery::Failed) => true, + // TODO: queued icon + Some(Delivery::Queued) => true, + _ => hovered, + }; + let delivery = if show_delivery { + message + .delivery + .map(|delivery| icons::delivery_to_icon_svg(delivery)) + .unwrap_or_default() + } else { + None + }; + let delivery: Container<Message> = if let Some(delivery) = delivery { + container(delivery) + } else { + container("") } - major_message = major_message.push(message_right); - major_message.spacing(8).into() + .width(16); + let major_message = row![avatar, message_right, horizontal_space(), delivery]; + container(major_message.spacing(8)) } else { - row![ - container(text(timestamp)).width(48), - text(&message.body.body) - ] - .spacing(8) - .into() - } + let timestamp = if hovered { + let mut thin = Font::with_name("K2D"); + thin.weight = Weight::Thin; + let timestamp = text(timestamp).size(10).font(thin); + container(timestamp).align_right(48) + } else { + container("").width(48) + }; + let show_delivery = match message.delivery { + Some(Delivery::Sending) => true, + Some(Delivery::Failed) => true, + // TODO: queued icon + Some(Delivery::Queued) => true, + _ => hovered, + }; + let delivery = if show_delivery { + message + .delivery + .map(|delivery| icons::delivery_to_icon_svg(delivery)) + .unwrap_or_default() + } else { + None + }; + let delivery: Container<Message> = if let Some(delivery) = delivery { + container(delivery) + } else { + container("") + } + .width(16); + container( + row![ + timestamp, + text(&message.body.body), + horizontal_space(), + delivery + ] + .align_y(Vertical::Top) + .spacing(8), + ) + }; + // let overlay = overlay::Element::new(Box::new(Overlay {})); + mouse_area( + container + .width(Length::Fill) + .style(move |theme| { + if hovered { + container::Style::default() + .background(theme.extended_palette().background.weak.color) + } else { + container::Style::default() + } + }) + .padding(Padding { + top: 4., + right: 8., + bottom: 4., + left: 8., + }), + ) + .on_enter(Message::MessageHovered(message.id)) + .on_exit(Message::MessageUnhovered(message.id)) + .into() } } |