aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar cel 🌸 <cel@bunny.garden>2025-04-22 18:23:29 +0100
committerLibravatar cel 🌸 <cel@bunny.garden>2025-04-22 18:23:29 +0100
commit8d159c86349803658e2b531ea7ca8e0471a7cc9c (patch)
treec7bcd671a00f1d5add7d1dbde10a13fda6984216 /src
parent14f6aaf18a3311fee63b11d0ec9c12ba76b70fa1 (diff)
downloadmacaw-8d159c86349803658e2b531ea7ca8e0471a7cc9c.tar.gz
macaw-8d159c86349803658e2b531ea7ca8e0471a7cc9c.tar.bz2
macaw-8d159c86349803658e2b531ea7ca8e0471a7cc9c.zip
feat: better styling for message widgetHEADmain
Diffstat (limited to '')
-rw-r--r--src/icons.rs93
-rw-r--r--src/main.rs8
-rw-r--r--src/message_view.rs194
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()
}
}