diff options
Diffstat (limited to 'src')
| -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()      }  } | 
