[fractal/split-appop-ui: 4/5] Check more invariants through the type system in MessageBox
- From: Alejandro Domínguez <aledomu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/split-appop-ui: 4/5] Check more invariants through the type system in MessageBox
- Date: Wed, 20 Jan 2021 10:53:50 +0000 (UTC)
commit 6f74afd3fd9c8843ccf47afdac5c4ea4c7f69d4c
Author: Alejandro Domínguez <adomu net-c com>
Date: Fri Jan 15 11:49:44 2021 +0100
Check more invariants through the type system in MessageBox
This modification improves encapsulation and follows the mantra
"make illegal state unrepresentable", while making the building
process less stateful.
fractal-gtk/src/appop/message.rs | 4 +-
fractal-gtk/src/widgets/message.rs | 751 +++++++++++++++++---------------
fractal-gtk/src/widgets/room_history.rs | 24 +-
3 files changed, 418 insertions(+), 361 deletions(-)
---
diff --git a/fractal-gtk/src/appop/message.rs b/fractal-gtk/src/appop/message.rs
index 887f9661..3a780c99 100644
--- a/fractal-gtk/src/appop/message.rs
+++ b/fractal-gtk/src/appop/message.rs
@@ -74,7 +74,7 @@ impl AppOp {
self.user_info_cache.clone(),
&ui_msg,
);
- let m = &mb.root;
+ let m = mb.get_widget();
messages.add(m);
if let Some(w) = messages.get_children().iter().last() {
@@ -114,7 +114,7 @@ impl AppOp {
self.user_info_cache.clone(),
&ui_msg,
);
- let m = &mb.root;
+ let m = mb.get_widget();
messages.add(m);
if let Some(w) = messages.get_children().iter().last() {
diff --git a/fractal-gtk/src/widgets/message.rs b/fractal-gtk/src/widgets/message.rs
index 3f40473d..766f72f5 100644
--- a/fractal-gtk/src/widgets/message.rs
+++ b/fractal-gtk/src/widgets/message.rs
@@ -19,40 +19,40 @@ use matrix_sdk::Client as MatrixClient;
use std::cmp::max;
use std::rc::Rc;
+#[derive(Clone, Debug)]
+pub enum MessageBoxMedia {
+ None,
+ Image(gtk::DrawingArea),
+ VideoPlayer(Rc<VideoPlayerWidget>),
+}
+
// A message row in the room history
#[derive(Clone, Debug)]
pub struct MessageBox {
- pub root: gtk::ListBoxRow,
- username: gtk::Label,
- pub username_event_box: gtk::EventBox,
+ root: gtk::ListBoxRow,
eventbox: gtk::EventBox,
gesture: gtk::GestureLongPress,
- image: Option<gtk::DrawingArea>,
- pub video_player: Option<Rc<VideoPlayerWidget>>,
- pub header: bool,
+ pub media_widget: MessageBoxMedia,
+ header: Option<MessageBoxInfoHeader>,
}
impl MessageBox {
fn new() -> Self {
- let username = gtk::Label::new(None);
- let username_event_box = gtk::EventBox::new();
let eventbox = gtk::EventBox::new();
+
let root = gtk::ListBoxRow::new();
- let gesture = gtk::GestureLongPress::new(&eventbox);
+ root.add(&eventbox);
- username.set_ellipsize(pango::EllipsizeMode::End);
+ let gesture = gtk::GestureLongPress::new(&eventbox);
gesture.set_propagation_phase(gtk::PropagationPhase::Capture);
gesture.set_touch_only(true);
Self {
root,
- username,
- username_event_box,
eventbox,
gesture,
- image: None,
- video_player: None,
- header: true,
+ media_widget: MessageBoxMedia::None,
+ header: None,
}
}
@@ -71,7 +71,6 @@ impl MessageBox {
let w = match msg.mtype {
RowType::Emote => {
mb.root.set_margin_top(12);
- mb.header = false;
mb.small_widget(session_client, msg)
}
RowType::Video if is_temp => {
@@ -95,17 +94,12 @@ impl MessageBox {
}
_ if has_header => {
mb.root.set_margin_top(12);
- mb.header = true;
mb.widget(session_client, user_info_cache, msg)
}
- _ => {
- mb.header = false;
- mb.small_widget(session_client, msg)
- }
+ _ => mb.small_widget(session_client, msg),
};
mb.eventbox.add(&w);
- mb.root.add(&mb.eventbox);
mb.root.show_all();
mb.connect_right_click_menu(msg, None);
@@ -131,13 +125,11 @@ impl MessageBox {
) {
let w = if has_header && msg.mtype != RowType::Emote {
self.root.set_margin_top(12);
- self.header = true;
self.widget(session_client, user_info_cache, &msg)
} else {
if let RowType::Emote = msg.mtype {
self.root.set_margin_top(12);
}
- self.header = false;
self.small_widget(session_client, &msg)
};
if let Some(eb) = self.eventbox.get_child() {
@@ -147,6 +139,21 @@ impl MessageBox {
self.root.show_all();
}
+ pub fn get_widget(&self) -> >k::ListBoxRow {
+ &self.root
+ }
+
+ pub fn get_video_player(&self) -> Option<&Rc<VideoPlayerWidget>> {
+ match self.media_widget {
+ MessageBoxMedia::VideoPlayer(ref player) => Some(player),
+ _ => None,
+ }
+ }
+
+ pub fn has_header(&self) -> bool {
+ self.header.is_some()
+ }
+
fn widget(
&mut self,
session_client: MatrixClient,
@@ -158,9 +165,9 @@ impl MessageBox {
// | avatar | content |
// +--------+---------+
let msg_widget = gtk::Box::new(gtk::Orientation::Horizontal, 10);
- let content = self.build_room_msg_content(session_client.clone(), msg, false);
+ let content = self.build_room_msg_content(session_client.clone(), msg, true);
// TODO: make build_room_msg_avatar() faster (currently ~1ms)
- let avatar = self.build_room_msg_avatar(session_client, user_info_cache, msg);
+ let avatar = build_room_msg_avatar(session_client, user_info_cache, msg);
msg_widget.pack_start(&avatar, false, false, 0);
msg_widget.pack_start(&content, true, true, 0);
@@ -174,7 +181,7 @@ impl MessageBox {
// | | content |
// +--------+---------+
let msg_widget = gtk::Box::new(gtk::Orientation::Horizontal, 5);
- let content = self.build_room_msg_content(session_client, msg, true);
+ let content = self.build_room_msg_content(session_client, msg, false);
content.set_margin_start(50);
msg_widget.pack_start(&content, true, true, 0);
@@ -186,7 +193,7 @@ impl MessageBox {
&mut self,
session_client: MatrixClient,
msg: &Message,
- small: bool,
+ info_header: bool,
) -> gtk::Box {
// content
// +---------+
@@ -196,12 +203,16 @@ impl MessageBox {
// +---------+
let content = gtk::Box::new(gtk::Orientation::Vertical, 0);
- if !small {
- let info = self.build_room_msg_info(msg);
- info.set_margin_top(2);
- info.set_margin_bottom(3);
- content.pack_start(&info, false, false, 0);
- }
+ self.header = if info_header {
+ let info = MessageBoxInfoHeader::from(msg);
+ info.root.set_margin_top(2);
+ info.root.set_margin_bottom(3);
+ content.pack_start(&info.root, false, false, 0);
+
+ Some(info)
+ } else {
+ None
+ };
let body_bx = self.build_room_msg_body_bx(session_client, msg);
content.pack_start(&body_bx, true, true, 0);
@@ -217,12 +228,34 @@ impl MessageBox {
let body_bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let body = match msg.mtype {
- RowType::Sticker => self.build_room_msg_sticker(session_client, msg),
- RowType::Image => self.build_room_msg_image(session_client, msg),
- RowType::Emote => self.build_room_msg_emote(msg),
- RowType::Audio => self.build_room_audio_player(session_client, msg),
- RowType::Video => self.build_room_video_player(session_client, msg),
- RowType::File => self.build_room_msg_file(msg),
+ RowType::Sticker => build_room_msg_sticker(session_client, msg),
+ RowType::Audio => build_room_audio_player(session_client, msg),
+ RowType::Image => {
+ let (image_box, image) = build_room_msg_image(session_client, msg);
+
+ if let Some(image) = image {
+ self.media_widget = MessageBoxMedia::Image(image.widget);
+ self.connect_media_viewer(msg);
+ }
+
+ image_box
+ }
+ RowType::Video => {
+ let (video_box, player) = build_room_video_player(session_client, msg);
+
+ if let Some(player) = player {
+ self.media_widget = MessageBoxMedia::VideoPlayer(player);
+ self.connect_media_viewer(msg);
+ }
+
+ video_box
+ }
+ RowType::Emote => {
+ let (emote_box, msg_label) = build_room_msg_emote(msg);
+ self.connect_right_click_menu(msg, Some(&msg_label));
+ emote_box
+ }
+ RowType::File => build_room_msg_file(msg),
_ => self.build_room_msg_body(msg),
};
@@ -239,48 +272,8 @@ impl MessageBox {
body_bx.pack_start(&edit_mark, false, false, 0);
}
- body_bx
- }
-
- fn build_room_msg_avatar(
- &self,
- session_client: MatrixClient,
- user_info_cache: UserInfoCache,
- msg: &Message,
- ) -> widgets::Avatar {
- let uid = msg.sender.clone();
- let alias = msg.sender_name.clone();
- let avatar = widgets::Avatar::avatar_new(Some(globals::MSG_ICON_SIZE));
- avatar.set_valign(gtk::Align::Start);
-
- let data = avatar.circle(
- uid.to_string(),
- alias.clone(),
- globals::MSG_ICON_SIZE,
- None,
- None,
- );
- self.username
- .set_text(alias.as_deref().unwrap_or(uid.as_str()));
-
- download_to_cache(
- session_client.clone(),
- user_info_cache,
- uid.clone(),
- data.clone(),
- );
-
- avatar
- }
-
- fn build_room_msg_username(&self, uname: &str) -> gtk::Label {
- self.username.set_text(uname);
- self.username.set_justify(gtk::Justification::Left);
- self.username.set_halign(gtk::Align::Start);
- self.username.get_style_context().add_class("username");
-
- self.username.clone()
+ body_bx
}
// Add classes to the widget based on message type
@@ -358,271 +351,6 @@ impl MessageBox {
bx
}
- fn build_room_msg_image(&mut self, session_client: MatrixClient, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
-
- // If the thumbnail is not a valid URL we use the msg.url
- let img = msg
- .thumb
- .clone()
- .filter(|m| m.scheme() == "mxc" || m.scheme().starts_with("http"))
- .or_else(|| msg.url.clone())
- .map(Either::Left)
- .or_else(|| Some(Either::Right(msg.local_path.clone()?)));
-
- if let Some(img_path) = img {
- let image = widgets::image::Image::new(img_path)
- .size(Some(globals::MAX_IMAGE_SIZE))
- .build(session_client);
-
- image.widget.get_style_context().add_class("image-widget");
-
- bx.pack_start(&image.widget, true, true, 0);
- bx.show_all();
- self.image = Some(image.widget);
- self.connect_media_viewer(msg);
- }
-
- bx
- }
-
- fn build_room_msg_sticker(&self, session_client: MatrixClient, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- if let Some(url) = msg.url.clone() {
- let image = widgets::image::Image::new(Either::Left(url))
- .size(Some(globals::MAX_STICKER_SIZE))
- .build(session_client);
- image.widget.set_tooltip_text(Some(&msg.body[..]));
-
- bx.add(&image.widget);
- }
-
- bx
- }
-
- fn build_room_audio_player(&self, session_client: MatrixClient, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Horizontal, 6);
-
- if let Some(url) = msg.url.clone() {
- let player = AudioPlayerWidget::new();
- let start_playing = false;
- PlayerExt::initialize_stream(
- player.clone(),
- session_client,
- url,
- bx.clone(),
- start_playing,
- );
-
- let control_box = PlayerExt::get_controls_container(&player)
- .expect("Every AudioPlayer must have controls.");
- bx.pack_start(&control_box, false, true, 0);
- }
-
- let download_btn =
- gtk::Button::from_icon_name(Some("document-save-symbolic"), gtk::IconSize::Button);
- download_btn.set_tooltip_text(Some(i18n("Save").as_str()));
-
- let evid = msg
- .id
- .as_ref()
- .map(|evid| evid.to_string())
- .unwrap_or_default();
- let data = glib::Variant::from(evid);
- download_btn.set_action_target_value(Some(&data));
- download_btn.set_action_name(Some("message.save_as"));
- bx.pack_start(&download_btn, false, false, 3);
-
- let outer_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
- let file_name = gtk::Label::new(Some(&format!("<b>{}</b>", msg.body)));
- file_name.set_use_markup(true);
- file_name.set_xalign(0.0);
- file_name.set_line_wrap(true);
- file_name.set_line_wrap_mode(pango::WrapMode::WordChar);
- outer_box.pack_start(&file_name, false, false, 0);
- outer_box.pack_start(&bx, false, false, 0);
- outer_box.get_style_context().add_class("audio-box");
- outer_box
- }
-
- fn build_room_video_player(&mut self, session_client: MatrixClient, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Vertical, 6);
-
- if let Some(url) = msg.url.clone() {
- let with_controls = false;
- let player = VideoPlayerWidget::new(with_controls);
- let start_playing = false;
- PlayerExt::initialize_stream(
- player.clone(),
- session_client,
- url,
- bx.clone(),
- start_playing,
- );
-
- let overlay = Overlay::new();
- let video_widget = player.get_video_widget();
- video_widget.set_size_request(-1, 390);
- VideoPlayerWidget::auto_adjust_video_dimensions(&player);
- overlay.add(&video_widget);
-
- let play_button = gtk::Button::new();
- let play_icon = gtk::Image::from_icon_name(
- Some("media-playback-start-symbolic"),
- gtk::IconSize::Dialog,
- );
- play_button.set_image(Some(&play_icon));
- play_button.set_halign(gtk::Align::Center);
- play_button.set_valign(gtk::Align::Center);
- play_button.get_style_context().add_class("osd");
- play_button.get_style_context().add_class("play-icon");
- play_button.get_style_context().add_class("flat");
- let evid = msg
- .id
- .as_ref()
- .map(|evid| evid.to_string())
- .unwrap_or_default();
- let data = glib::Variant::from(evid);
- play_button.set_action_name(Some("app.open-media-viewer"));
- play_button.set_action_target_value(Some(&data));
- overlay.add_overlay(&play_button);
-
- let menu_button = gtk::MenuButton::new();
- let three_dot_icon =
- gtk::Image::from_icon_name(Some("view-more-symbolic"), gtk::IconSize::Button);
- menu_button.set_image(Some(&three_dot_icon));
- menu_button.get_style_context().add_class("osd");
- menu_button.get_style_context().add_class("round-button");
- menu_button.get_style_context().add_class("flat");
- menu_button.set_margin_top(12);
- menu_button.set_margin_end(12);
- menu_button.set_opacity(0.8);
- menu_button.set_halign(gtk::Align::End);
- menu_button.set_valign(gtk::Align::Start);
- menu_button.connect_size_allocate(|button, allocation| {
- let diameter = max(allocation.width, allocation.height);
- button.set_size_request(diameter, diameter);
- });
- overlay.add_overlay(&menu_button);
-
- let evid = msg.id.as_ref();
- let redactable = msg.redactable;
- let menu = MessageMenu::new(evid, &RowType::Video, &redactable, None);
- menu_button.set_popover(Some(&menu.get_popover()));
-
- let clip_container = ClipContainer::new();
- clip_container.add(&overlay);
-
- bx.pack_start(&clip_container, true, true, 0);
- self.connect_media_viewer(msg);
- self.video_player = Some(player);
- }
- bx
- }
-
- fn build_room_msg_file(&self, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Horizontal, 12);
- let btn_bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
-
- let name = msg.body.as_str();
- let name_lbl = gtk::Label::new(Some(name));
- name_lbl.set_tooltip_text(Some(name));
- name_lbl.set_ellipsize(pango::EllipsizeMode::End);
-
- name_lbl.get_style_context().add_class("msg-highlighted");
-
- let download_btn =
- gtk::Button::from_icon_name(Some("document-save-symbolic"), gtk::IconSize::Button);
- download_btn.set_tooltip_text(Some(i18n("Save").as_str()));
-
- let evid = msg
- .id
- .as_ref()
- .map(|evid| evid.to_string())
- .unwrap_or_default();
-
- let data = glib::Variant::from(&evid);
- download_btn.set_action_target_value(Some(&data));
- download_btn.set_action_name(Some("message.save_as"));
-
- let open_btn =
- gtk::Button::from_icon_name(Some("document-open-symbolic"), gtk::IconSize::Button);
- open_btn.set_tooltip_text(Some(i18n("Open").as_str()));
-
- let data = glib::Variant::from(&evid);
- open_btn.set_action_target_value(Some(&data));
- open_btn.set_action_name(Some("message.open_with"));
-
- btn_bx.pack_start(&open_btn, false, false, 0);
- btn_bx.pack_start(&download_btn, false, false, 0);
- btn_bx.get_style_context().add_class("linked");
-
- bx.pack_start(&name_lbl, false, false, 0);
- bx.pack_start(&btn_bx, false, false, 0);
- bx
- }
-
- fn build_room_msg_date(&self, dt: &DateTime<Local>) -> gtk::Label {
- // TODO: get system preference for 12h/24h
- let use_ampm = false;
- let format = if use_ampm {
- // Use 12h time format (AM/PM)
- i18n("%l∶%M %p")
- } else {
- // Use 24 time format
- i18n("%R")
- };
-
- let d = dt.format(&format).to_string();
-
- let date = gtk::Label::new(None);
- date.set_markup(&format!("<span alpha=\"60%\">{}</span>", d.trim()));
- date.set_line_wrap(true);
- date.set_justify(gtk::Justification::Right);
- date.set_valign(gtk::Align::Start);
- date.set_halign(gtk::Align::End);
- date.get_style_context().add_class("timestamp");
-
- date
- }
-
- fn build_room_msg_info(&self, msg: &Message) -> gtk::Box {
- // info
- // +----------+------+
- // | username | date |
- // +----------+------+
- let info = gtk::Box::new(gtk::Orientation::Horizontal, 0);
-
- let username =
- self.build_room_msg_username(msg.sender_name.as_deref().unwrap_or(msg.sender.as_str()));
- let date = self.build_room_msg_date(&msg.date);
-
- self.username_event_box.add(&username);
-
- info.pack_start(&self.username_event_box, true, true, 0);
- info.pack_start(&date, false, false, 0);
-
- info
- }
-
- fn build_room_msg_emote(&self, msg: &Message) -> gtk::Box {
- let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
- // Use MXID till we have a alias
- let sname = msg
- .sender_name
- .clone()
- .unwrap_or_else(|| msg.sender.to_string());
- let msg_label = gtk::Label::new(None);
- let markup = markup_text(&msg.body);
-
- self.connect_right_click_menu(msg, Some(&msg_label));
- msg_label.set_markup(&format!("<b>{}</b> {}", sname, markup));
- set_label_styles(&msg_label);
-
- bx.add(&msg_label);
- bx
- }
-
fn connect_right_click_menu(&self, msg: &Message, label: Option<>k::Label>) -> Option<()> {
let mtype = msg.mtype;
let redactable = msg.redactable;
@@ -662,6 +390,267 @@ impl MessageBox {
}
}
+fn build_room_msg_avatar(
+ session_client: MatrixClient,
+ user_info_cache: UserInfoCache,
+ msg: &Message,
+) -> widgets::Avatar {
+ let uid = msg.sender.clone();
+ let alias = msg.sender_name.clone();
+ let avatar = widgets::Avatar::avatar_new(Some(globals::MSG_ICON_SIZE));
+ avatar.set_valign(gtk::Align::Start);
+
+ let data = avatar.circle(
+ uid.to_string(),
+ alias.clone(),
+ globals::MSG_ICON_SIZE,
+ None,
+ None,
+ );
+
+ download_to_cache(
+ session_client.clone(),
+ user_info_cache,
+ uid.clone(),
+ data.clone(),
+ );
+
+ avatar
+}
+
+fn build_room_msg_sticker(session_client: MatrixClient, msg: &Message) -> gtk::Box {
+ let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+ if let Some(url) = msg.url.clone() {
+ let image = widgets::image::Image::new(Either::Left(url))
+ .size(Some(globals::MAX_STICKER_SIZE))
+ .build(session_client);
+ image.widget.set_tooltip_text(Some(&msg.body[..]));
+
+ bx.add(&image.widget);
+ }
+
+ bx
+}
+
+fn build_room_audio_player(session_client: MatrixClient, msg: &Message) -> gtk::Box {
+ let bx = gtk::Box::new(gtk::Orientation::Horizontal, 6);
+
+ if let Some(url) = msg.url.clone() {
+ let player = AudioPlayerWidget::new();
+ let start_playing = false;
+ PlayerExt::initialize_stream(
+ player.clone(),
+ session_client,
+ url,
+ bx.clone(),
+ start_playing,
+ );
+
+ let control_box = PlayerExt::get_controls_container(&player)
+ .expect("Every AudioPlayer must have controls.");
+ bx.pack_start(&control_box, false, true, 0);
+ }
+
+ let download_btn =
+ gtk::Button::from_icon_name(Some("document-save-symbolic"), gtk::IconSize::Button);
+ download_btn.set_tooltip_text(Some(i18n("Save").as_str()));
+
+ let evid = msg
+ .id
+ .as_ref()
+ .map(|evid| evid.to_string())
+ .unwrap_or_default();
+ let data = glib::Variant::from(evid);
+ download_btn.set_action_target_value(Some(&data));
+ download_btn.set_action_name(Some("message.save_as"));
+ bx.pack_start(&download_btn, false, false, 3);
+
+ let outer_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
+ let file_name = gtk::Label::new(Some(&format!("<b>{}</b>", msg.body)));
+ file_name.set_use_markup(true);
+ file_name.set_xalign(0.0);
+ file_name.set_line_wrap(true);
+ file_name.set_line_wrap_mode(pango::WrapMode::WordChar);
+ outer_box.pack_start(&file_name, false, false, 0);
+ outer_box.pack_start(&bx, false, false, 0);
+ outer_box.get_style_context().add_class("audio-box");
+ outer_box
+}
+
+fn build_room_msg_file(msg: &Message) -> gtk::Box {
+ let bx = gtk::Box::new(gtk::Orientation::Horizontal, 12);
+ let btn_bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+
+ let name = msg.body.as_str();
+ let name_lbl = gtk::Label::new(Some(name));
+ name_lbl.set_tooltip_text(Some(name));
+ name_lbl.set_ellipsize(pango::EllipsizeMode::End);
+
+ name_lbl.get_style_context().add_class("msg-highlighted");
+
+ let download_btn =
+ gtk::Button::from_icon_name(Some("document-save-symbolic"), gtk::IconSize::Button);
+ download_btn.set_tooltip_text(Some(i18n("Save").as_str()));
+
+ let evid = msg
+ .id
+ .as_ref()
+ .map(|evid| evid.to_string())
+ .unwrap_or_default();
+
+ let data = glib::Variant::from(&evid);
+ download_btn.set_action_target_value(Some(&data));
+ download_btn.set_action_name(Some("message.save_as"));
+
+ let open_btn =
+ gtk::Button::from_icon_name(Some("document-open-symbolic"), gtk::IconSize::Button);
+ open_btn.set_tooltip_text(Some(i18n("Open").as_str()));
+
+ let data = glib::Variant::from(&evid);
+ open_btn.set_action_target_value(Some(&data));
+ open_btn.set_action_name(Some("message.open_with"));
+
+ btn_bx.pack_start(&open_btn, false, false, 0);
+ btn_bx.pack_start(&download_btn, false, false, 0);
+ btn_bx.get_style_context().add_class("linked");
+
+ bx.pack_start(&name_lbl, false, false, 0);
+ bx.pack_start(&btn_bx, false, false, 0);
+ bx
+}
+
+fn build_room_msg_image(
+ session_client: MatrixClient,
+ msg: &Message,
+) -> (gtk::Box, Option<widgets::image::Image>) {
+ let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+
+ // If the thumbnail is not a valid URL we use the msg.url
+ let img = msg
+ .thumb
+ .clone()
+ .filter(|m| m.scheme() == "mxc" || m.scheme().starts_with("http"))
+ .or_else(|| msg.url.clone())
+ .map(Either::Left)
+ .or_else(|| Some(Either::Right(msg.local_path.clone()?)));
+
+ let image = if let Some(img_path) = img {
+ let image = widgets::image::Image::new(img_path)
+ .size(Some(globals::MAX_IMAGE_SIZE))
+ .build(session_client);
+
+ image.widget.get_style_context().add_class("image-widget");
+
+ bx.pack_start(&image.widget, true, true, 0);
+ bx.show_all();
+
+ Some(image)
+ } else {
+ None
+ };
+
+ (bx, image)
+}
+
+fn build_room_video_player(
+ session_client: MatrixClient,
+ msg: &Message,
+) -> (gtk::Box, Option<Rc<VideoPlayerWidget>>) {
+ let bx = gtk::Box::new(gtk::Orientation::Vertical, 6);
+
+ let player = if let Some(url) = msg.url.clone() {
+ let with_controls = false;
+ let player = VideoPlayerWidget::new(with_controls);
+ let start_playing = false;
+ PlayerExt::initialize_stream(
+ player.clone(),
+ session_client,
+ url,
+ bx.clone(),
+ start_playing,
+ );
+
+ let overlay = Overlay::new();
+ let video_widget = player.get_video_widget();
+ video_widget.set_size_request(-1, 390);
+ VideoPlayerWidget::auto_adjust_video_dimensions(&player);
+ overlay.add(&video_widget);
+
+ let play_button = gtk::Button::new();
+ let play_icon = gtk::Image::from_icon_name(
+ Some("media-playback-start-symbolic"),
+ gtk::IconSize::Dialog,
+ );
+ play_button.set_image(Some(&play_icon));
+ play_button.set_halign(gtk::Align::Center);
+ play_button.set_valign(gtk::Align::Center);
+ play_button.get_style_context().add_class("osd");
+ play_button.get_style_context().add_class("play-icon");
+ play_button.get_style_context().add_class("flat");
+ let evid = msg
+ .id
+ .as_ref()
+ .map(|evid| evid.to_string())
+ .unwrap_or_default();
+ let data = glib::Variant::from(evid);
+ play_button.set_action_name(Some("app.open-media-viewer"));
+ play_button.set_action_target_value(Some(&data));
+ overlay.add_overlay(&play_button);
+
+ let menu_button = gtk::MenuButton::new();
+ let three_dot_icon =
+ gtk::Image::from_icon_name(Some("view-more-symbolic"), gtk::IconSize::Button);
+ menu_button.set_image(Some(&three_dot_icon));
+ menu_button.get_style_context().add_class("osd");
+ menu_button.get_style_context().add_class("round-button");
+ menu_button.get_style_context().add_class("flat");
+ menu_button.set_margin_top(12);
+ menu_button.set_margin_end(12);
+ menu_button.set_opacity(0.8);
+ menu_button.set_halign(gtk::Align::End);
+ menu_button.set_valign(gtk::Align::Start);
+ menu_button.connect_size_allocate(|button, allocation| {
+ let diameter = max(allocation.width, allocation.height);
+ button.set_size_request(diameter, diameter);
+ });
+ overlay.add_overlay(&menu_button);
+
+ let evid = msg.id.as_ref();
+ let redactable = msg.redactable;
+ let menu = MessageMenu::new(evid, &RowType::Video, &redactable, None);
+ menu_button.set_popover(Some(&menu.get_popover()));
+
+ let clip_container = ClipContainer::new();
+ clip_container.add(&overlay);
+
+ bx.pack_start(&clip_container, true, true, 0);
+
+ Some(player)
+ } else {
+ None
+ };
+
+ (bx, player)
+}
+
+fn build_room_msg_emote(msg: &Message) -> (gtk::Box, gtk::Label) {
+ let bx = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+ // Use MXID till we have a alias
+ let sname = msg
+ .sender_name
+ .clone()
+ .unwrap_or_else(|| msg.sender.to_string());
+ let msg_label = gtk::Label::new(None);
+ let markup = markup_text(&msg.body);
+
+ msg_label.set_markup(&format!("<b>{}</b> {}", sname, markup));
+ set_label_styles(&msg_label);
+
+ bx.add(&msg_label);
+
+ (bx, msg_label)
+}
+
fn set_label_styles(w: >k::Label) {
w.set_line_wrap(true);
w.set_line_wrap_mode(pango::WrapMode::WordChar);
@@ -745,6 +734,76 @@ fn highlight_username(
None
}
+#[derive(Clone, Debug)]
+pub struct MessageBoxInfoHeader {
+ root: gtk::Box,
+ username_event_box: gtk::EventBox,
+ username: gtk::Label,
+ date: gtk::Label,
+}
+
+impl From<&Message> for MessageBoxInfoHeader {
+ fn from(msg: &Message) -> Self {
+ // info
+ // +----------+------+
+ // | username | date |
+ // +----------+------+
+ let root = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+
+ let username =
+ build_room_msg_username(msg.sender_name.as_deref().unwrap_or(msg.sender.as_str()));
+ let date = build_room_msg_date(&msg.date);
+
+ let username_event_box = gtk::EventBox::new();
+ username_event_box.add(&username);
+
+ root.pack_start(&username_event_box, true, true, 0);
+ root.pack_start(&date, false, false, 0);
+
+ Self {
+ root,
+ username_event_box,
+ username,
+ date,
+ }
+ }
+}
+
+fn build_room_msg_username(uname: &str) -> gtk::Label {
+ let username = gtk::Label::new(Some(uname));
+
+ username.set_ellipsize(pango::EllipsizeMode::End);
+ username.set_justify(gtk::Justification::Left);
+ username.set_halign(gtk::Align::Start);
+ username.get_style_context().add_class("username");
+
+ username
+}
+
+fn build_room_msg_date(dt: &DateTime<Local>) -> gtk::Label {
+ // TODO: get system preference for 12h/24h
+ let use_ampm = false;
+ let format = if use_ampm {
+ // Use 12h time format (AM/PM)
+ i18n("%l∶%M %p")
+ } else {
+ // Use 24 time format
+ i18n("%R")
+ };
+
+ let d = dt.format(&format).to_string();
+
+ let date = gtk::Label::new(None);
+ date.set_markup(&format!("<span alpha=\"60%\">{}</span>", d.trim()));
+ date.set_line_wrap(true);
+ date.set_justify(gtk::Justification::Right);
+ date.set_valign(gtk::Align::Start);
+ date.set_halign(gtk::Align::End);
+ date.get_style_context().add_class("timestamp");
+
+ date
+}
+
#[derive(PartialEq)]
enum MsgPartType {
Normal,
diff --git a/fractal-gtk/src/widgets/room_history.rs b/fractal-gtk/src/widgets/room_history.rs
index 5ccb8ff1..34b6135f 100644
--- a/fractal-gtk/src/widgets/room_history.rs
+++ b/fractal-gtk/src/widgets/room_history.rs
@@ -137,9 +137,9 @@ impl List {
Some(content
.widget
.as_ref()?
- .video_player
- .clone()
+ .get_video_player()
.expect("The widget of every MessageContent, whose mtype is RowType::Video, must
have a video_player.")
+ .clone()
)
},
_ => None,
@@ -255,13 +255,11 @@ enum Element {
impl Element {
fn get_listbox_row(&self) -> >k::ListBoxRow {
match self {
- Element::Message(content) => {
- &content
- .widget
- .as_ref()
- .expect("The content of every message element must have widget.")
- .root
- }
+ Element::Message(content) => content
+ .widget
+ .as_ref()
+ .expect("The content of every message element must have widget.")
+ .get_widget(),
Element::NewDivider(widgets) => widgets.get_widget(),
Element::DayDivider(widget) => widget,
}
@@ -626,7 +624,7 @@ impl RoomHistory {
session_client,
user_info_cache,
item.clone(),
- msg_widget.header,
+ msg_widget.has_header(),
&self.rows,
));
rows.replace_item(i, Element::Message(item));
@@ -658,7 +656,7 @@ impl RoomHistory {
// If the redacted message was a header message let's set
// the header on the next non-redacted message instead.
- if msg_widget.header {
+ if msg_widget.has_header() {
let rows_list_len = rows.list.len();
if let Some((msg_next_cloned, msg_widget)) = rows
.list
@@ -752,8 +750,8 @@ fn create_row(
if let RowType::Video = row.mtype {
/* The followign callback requires `Send` but is handled by the gtk main loop */
let fragile_rows = Fragile::new(Rc::downgrade(rows));
- PlayerExt::get_player(mb.video_player
- .as_ref()
+ PlayerExt::get_player(mb
+ .get_video_player()
.expect("The widget of every MessageContent, whose mtype is RowType::Video, must have a
video_player."))
.connect_uri_loaded(move |player, _| {
if let Some(rows) = fragile_rows.get().upgrade() {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]