session: Add Media Viewer
This commit is contained in:
parent
a92c21770a
commit
f21eccfc15
13 changed files with 470 additions and 11 deletions
|
@ -22,6 +22,7 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks" alias="event-menu.ui">ui/event-menu.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="event-source-dialog.ui">ui/event-source-dialog.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="login.ui">ui/login.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="media-viewer.ui">ui/media-viewer.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks" alias="sidebar-account-switcher.ui">ui/sidebar-account-switcher.ui</file>
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="GtkListItem">
|
||||
<property name="activatable">False</property>
|
||||
<binding name="activatable">
|
||||
<lookup name="activatable">
|
||||
<lookup name="item">row</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<property name="selectable">False</property>
|
||||
<property name="child">
|
||||
<object class="ContentItemRow">
|
||||
<object class="ContentItemRow" id="row">
|
||||
<binding name="item">
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
<lookup name="item">GtkListItem</lookup>
|
||||
</binding>
|
||||
</object>
|
||||
</property>
|
||||
|
|
|
@ -142,6 +142,7 @@
|
|||
<property name="resource">/org/gnome/FractalNext/content-item.ui</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="single-click-activate">True</property>
|
||||
<accessibility>
|
||||
<property name="label" translatable="yes">Room History</property>
|
||||
</accessibility>
|
||||
|
@ -227,4 +228,3 @@
|
|||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
|
||||
|
|
93
data/resources/ui/media-viewer.ui
Normal file
93
data/resources/ui/media-viewer.ui
Normal file
|
@ -0,0 +1,93 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<template class="MediaViewer" parent="ContextMenuBin">
|
||||
<property name="child">
|
||||
<object class="GtkOverlay">
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkHeaderBar">
|
||||
<property name="visible" bind-source="MediaViewer" bind-property="fullscreened" bind-flags="sync-create | invert-boolean"/>
|
||||
<property name="title-widget">
|
||||
<object class="GtkLabel">
|
||||
<binding name="label">
|
||||
<lookup name="body">MediaViewer</lookup>
|
||||
</binding>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<style>
|
||||
<class name="title"/>
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="back">
|
||||
<property name="icon-name">go-previous-symbolic</property>
|
||||
<property name="action-name">session.show-content</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="menu_unfull">
|
||||
<property name="icon-name">view-more-symbolic</property>
|
||||
<property name="menu-model" bind-source="MediaViewer" bind-property="context-menu" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">view-fullscreen-symbolic</property>
|
||||
<property name="action-name">win.toggle-fullscreen</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwBin" id="media">
|
||||
<property name="halign">center</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="overlay">
|
||||
<object class="GtkRevealer" id="headerbar_revealer">
|
||||
<property name="visible" bind-source="MediaViewer" bind-property="fullscreened" bind-flags="sync-create"/>
|
||||
<property name="valign">GTK_ALIGN_START</property>
|
||||
<property name="transition-type">GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN</property>
|
||||
<child>
|
||||
<object class="GtkHeaderBar">
|
||||
<property name="show-title-buttons">false</property>
|
||||
<property name="title-widget">
|
||||
<object class="GtkLabel">
|
||||
<binding name="label">
|
||||
<lookup name="body">MediaViewer</lookup>
|
||||
</binding>
|
||||
<property name="single-line-mode">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
<style>
|
||||
<class name="title"/>
|
||||
</style>
|
||||
</object>
|
||||
</property>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="menu_full">
|
||||
<property name="icon-name">view-more-symbolic</property>
|
||||
<property name="menu-model" bind-source="MediaViewer" bind-property="context-menu" bind-flags="sync-create"/>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">view-restore-symbolic</property>
|
||||
<property name="action-name">win.toggle-fullscreen</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<layout>
|
||||
<property name="measure">True</property>
|
||||
</layout>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
|
@ -61,8 +61,10 @@
|
|||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="MediaViewer" id="media_viewer" />
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ data/resources/ui/event-menu.ui
|
|||
data/resources/ui/event-source-dialog.ui
|
||||
data/resources/ui/login.ui
|
||||
data/resources/ui/in-app-notification.ui
|
||||
data/resources/ui/media-viewer.ui
|
||||
data/resources/ui/room-creation.ui
|
||||
data/resources/ui/session.ui
|
||||
data/resources/ui/session-verification.ui
|
||||
|
@ -86,6 +87,7 @@ src/session/content/room_history/message_row/mod.rs
|
|||
src/session/content/room_history/message_row/text.rs
|
||||
src/session/content/room_history/mod.rs
|
||||
src/session/content/room_history/state_row.rs
|
||||
src/session/media_viewer.rs
|
||||
src/session/mod.rs
|
||||
src/session/room_creation/mod.rs
|
||||
src/session/room_list.rs
|
||||
|
|
|
@ -71,7 +71,7 @@ sources = files(
|
|||
'session/content/mod.rs',
|
||||
'session/content/room_details/member_page.rs',
|
||||
'session/content/room_details/mod.rs',
|
||||
'session/room/event_actions.rs',
|
||||
'session/media_viewer.rs',
|
||||
'session/room/event.rs',
|
||||
'session/room/highlight_flags.rs',
|
||||
'session/room/item.rs',
|
||||
|
|
|
@ -16,7 +16,7 @@ use sourceview::prelude::*;
|
|||
|
||||
use crate::components::{CustomEntry, RoomTitle};
|
||||
use crate::session::content::{MarkdownPopover, RoomDetails};
|
||||
use crate::session::room::{Room, RoomType};
|
||||
use crate::session::room::{Item, Room, RoomType};
|
||||
|
||||
mod imp {
|
||||
use super::*;
|
||||
|
@ -193,6 +193,21 @@ mod imp {
|
|||
self.listview
|
||||
.set_vscroll_policy(gtk::ScrollablePolicy::Natural);
|
||||
|
||||
self.listview
|
||||
.connect_activate(clone!(@weak obj => move |listview, pos| {
|
||||
if let Some(item) = listview
|
||||
.model()
|
||||
.and_then(|model| model.item(pos))
|
||||
.and_then(|o| o.downcast::<Item>().ok())
|
||||
{
|
||||
if let Some(event) = item.event() {
|
||||
if let Some(room) = obj.room() {
|
||||
room.session().show_media(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
obj.set_sticky(true);
|
||||
let adj = self.listview.vadjustment().unwrap();
|
||||
|
||||
|
|
272
src/session/media_viewer.rs
Normal file
272
src/session/media_viewer.rs
Normal file
|
@ -0,0 +1,272 @@
|
|||
use adw::{prelude::*, subclass::prelude::*};
|
||||
use gettextrs::gettext;
|
||||
use gtk::{
|
||||
gdk, gdk_pixbuf::Pixbuf, gio, glib, glib::clone, subclass::prelude::*, CompositeTemplate,
|
||||
};
|
||||
use log::warn;
|
||||
use matrix_sdk::ruma::events::{room::message::MessageType, AnyMessageEventContent};
|
||||
|
||||
use crate::{
|
||||
components::{ContextMenuBin, ContextMenuBinImpl},
|
||||
session::room::Event,
|
||||
spawn, Window,
|
||||
};
|
||||
|
||||
use super::room::EventActions;
|
||||
|
||||
mod imp {
|
||||
use crate::components::ContextMenuBinExt;
|
||||
|
||||
use super::*;
|
||||
use glib::object::WeakRef;
|
||||
use glib::subclass::InitializingObject;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::cell::{Cell, RefCell};
|
||||
|
||||
#[derive(Debug, Default, CompositeTemplate)]
|
||||
#[template(resource = "/org/gnome/FractalNext/media-viewer.ui")]
|
||||
pub struct MediaViewer {
|
||||
pub fullscreened: Cell<bool>,
|
||||
pub event: RefCell<Option<WeakRef<Event>>>,
|
||||
pub body: RefCell<Option<String>>,
|
||||
#[template_child]
|
||||
pub headerbar_revealer: TemplateChild<gtk::Revealer>,
|
||||
#[template_child]
|
||||
pub menu_full: TemplateChild<gtk::MenuButton>,
|
||||
#[template_child]
|
||||
pub media: TemplateChild<adw::Bin>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for MediaViewer {
|
||||
const NAME: &'static str = "MediaViewer";
|
||||
type Type = super::MediaViewer;
|
||||
type ParentType = ContextMenuBin;
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
Self::bind_template(klass);
|
||||
}
|
||||
|
||||
fn instance_init(obj: &InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectImpl for MediaViewer {
|
||||
fn properties() -> &'static [glib::ParamSpec] {
|
||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||
vec![
|
||||
glib::ParamSpec::new_boolean(
|
||||
"fullscreened",
|
||||
"Fullscreened",
|
||||
"Whether the viewer is fullscreen",
|
||||
false,
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_object(
|
||||
"event",
|
||||
"Event",
|
||||
"The media event to display",
|
||||
Event::static_type(),
|
||||
glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
|
||||
),
|
||||
glib::ParamSpec::new_string(
|
||||
"body",
|
||||
"Body",
|
||||
"The body of the media event",
|
||||
None,
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
PROPERTIES.as_ref()
|
||||
}
|
||||
|
||||
fn set_property(
|
||||
&self,
|
||||
obj: &Self::Type,
|
||||
_id: usize,
|
||||
value: &glib::Value,
|
||||
pspec: &glib::ParamSpec,
|
||||
) {
|
||||
match pspec.name() {
|
||||
"fullscreened" => obj.set_fullscreened(value.get().unwrap()),
|
||||
"event" => obj.set_event(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||
match pspec.name() {
|
||||
"fullscreened" => obj.fullscreened().to_value(),
|
||||
"event" => obj.event().to_value(),
|
||||
"body" => obj.body().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
obj.set_context_menu(Some(Self::Type::event_menu_model()));
|
||||
|
||||
// Bind `fullscreened` to the window property of the same name.
|
||||
obj.connect_notify_local(Some("root"), |obj, _| {
|
||||
if let Some(window) = obj.root().and_then(|root| root.downcast::<Window>().ok()) {
|
||||
window
|
||||
.bind_property("fullscreened", obj, "fullscreened")
|
||||
.flags(glib::BindingFlags::SYNC_CREATE)
|
||||
.build();
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle fullscreen on double click.
|
||||
let click_gesture = gtk::GestureClick::builder().button(1).build();
|
||||
click_gesture.connect_pressed(clone!(@weak obj => move |_, n_pressed, _, _| {
|
||||
if n_pressed == 2 {
|
||||
obj.activate_action("win.toggle-fullscreen", None);
|
||||
}
|
||||
}));
|
||||
obj.add_controller(&click_gesture);
|
||||
|
||||
// Show headerbar when revealer is hovered.
|
||||
let revealer: >k::Revealer = &*self.headerbar_revealer;
|
||||
let menu: >k::MenuButton = &*self.menu_full;
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
motion_controller.connect_enter(clone!(@weak revealer => move |_, _, _| {
|
||||
revealer.set_reveal_child(true);
|
||||
}));
|
||||
// Hide the headerbar when revealer is not hovered and header menu is closed.
|
||||
motion_controller.connect_leave(clone!(@weak revealer, @weak menu => move |_| {
|
||||
if menu.popover().filter(|popover| popover.is_visible()).is_none() {
|
||||
revealer.set_reveal_child(false);
|
||||
}
|
||||
}));
|
||||
menu.popover().unwrap().connect_closed(
|
||||
clone!(@weak revealer, @weak motion_controller, => move |_| {
|
||||
if !motion_controller.contains_pointer() {
|
||||
revealer.set_reveal_child(false);
|
||||
}
|
||||
}),
|
||||
);
|
||||
revealer.add_controller(&motion_controller);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for MediaViewer {}
|
||||
impl BinImpl for MediaViewer {}
|
||||
impl ContextMenuBinImpl for MediaViewer {}
|
||||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct MediaViewer(ObjectSubclass<imp::MediaViewer>)
|
||||
@extends gtk::Widget, adw::Bin, ContextMenuBin, @implements gtk::Accessible;
|
||||
}
|
||||
|
||||
impl MediaViewer {
|
||||
pub fn new() -> Self {
|
||||
glib::Object::new(&[]).expect("Failed to create MediaViewer")
|
||||
}
|
||||
|
||||
pub fn event(&self) -> Option<Event> {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
priv_
|
||||
.event
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|event| event.upgrade())
|
||||
}
|
||||
|
||||
pub fn set_event(&self, event: Option<Event>) {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
|
||||
if event == self.event() {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.event.replace(event.map(|event| event.downgrade()));
|
||||
self.build();
|
||||
self.notify("event");
|
||||
}
|
||||
|
||||
pub fn body(&self) -> Option<String> {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
priv_.body.borrow().clone()
|
||||
}
|
||||
|
||||
pub fn set_body(&self, body: Option<String>) {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
|
||||
if body == self.body() {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.body.replace(body);
|
||||
self.notify("body");
|
||||
}
|
||||
|
||||
pub fn fullscreened(&self) -> bool {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
priv_.fullscreened.get()
|
||||
}
|
||||
|
||||
pub fn set_fullscreened(&self, fullscreened: bool) {
|
||||
let priv_ = imp::MediaViewer::from_instance(self);
|
||||
|
||||
if fullscreened == self.fullscreened() {
|
||||
return;
|
||||
}
|
||||
|
||||
priv_.fullscreened.set(fullscreened);
|
||||
|
||||
// Upscale the media on fullscreen
|
||||
if fullscreened {
|
||||
priv_.media.set_halign(gtk::Align::Fill);
|
||||
} else {
|
||||
priv_.media.set_halign(gtk::Align::Center);
|
||||
}
|
||||
|
||||
self.notify("fullscreened");
|
||||
}
|
||||
|
||||
fn build(&self) {
|
||||
if let Some(event) = self.event() {
|
||||
self.set_event_actions(Some(&event));
|
||||
if let Some(AnyMessageEventContent::RoomMessage(content)) = event.message_content() {
|
||||
match content.msgtype {
|
||||
MessageType::Image(image) => {
|
||||
self.set_body(Some(image.body.clone()));
|
||||
|
||||
spawn!(
|
||||
glib::PRIORITY_LOW,
|
||||
clone!(@weak self as obj => async move {
|
||||
let priv_ = imp::MediaViewer::from_instance(&obj);
|
||||
|
||||
match event.get_media_content().await {
|
||||
Ok((_, data)) => {
|
||||
let stream = gio::MemoryInputStream::from_bytes(&glib::Bytes::from(&data));
|
||||
let texture = Pixbuf::from_stream(&stream, gio::NONE_CANCELLABLE)
|
||||
.ok()
|
||||
.map(|pixbuf| gdk::Texture::for_pixbuf(&pixbuf));
|
||||
let child = gtk::Picture::for_paintable(texture.as_ref());
|
||||
|
||||
priv_.media.set_child(Some(&child));
|
||||
}
|
||||
Err(error) => {
|
||||
warn!("Could not retrieve image file: {}", error);
|
||||
let child = gtk::Label::new(Some(&gettext("Could not retrieve image")));
|
||||
priv_.media.set_child(Some(&child));
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventActions for MediaViewer {}
|
|
@ -2,6 +2,7 @@ mod account_settings;
|
|||
mod avatar;
|
||||
mod content;
|
||||
mod event_source_dialog;
|
||||
mod media_viewer;
|
||||
pub mod room;
|
||||
mod room_creation;
|
||||
mod room_list;
|
||||
|
@ -12,7 +13,8 @@ pub mod verification;
|
|||
use self::account_settings::AccountSettings;
|
||||
pub use self::avatar::Avatar;
|
||||
use self::content::Content;
|
||||
pub use self::room::Room;
|
||||
use self::media_viewer::MediaViewer;
|
||||
pub use self::room::{Event, Item, Room};
|
||||
pub use self::room_creation::RoomCreation;
|
||||
use self::room_list::RoomList;
|
||||
use self::sidebar::Sidebar;
|
||||
|
@ -75,6 +77,8 @@ mod imp {
|
|||
pub content: TemplateChild<adw::Leaflet>,
|
||||
#[template_child]
|
||||
pub sidebar: TemplateChild<Sidebar>,
|
||||
#[template_child]
|
||||
pub media_viewer: TemplateChild<MediaViewer>,
|
||||
pub client: RefCell<Option<Client>>,
|
||||
pub item_list: OnceCell<ItemList>,
|
||||
pub user: OnceCell<User>,
|
||||
|
@ -728,6 +732,14 @@ impl Session {
|
|||
|
||||
self.emit_by_name("ready", &[]).unwrap();
|
||||
}
|
||||
|
||||
/// Show a media event
|
||||
pub fn show_media(&self, event: &Event) {
|
||||
let priv_ = imp::Session::from_instance(self);
|
||||
priv_.media_viewer.set_event(Some(event.clone()));
|
||||
|
||||
priv_.stack.set_visible_child(&*priv_.media_viewer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Session {
|
||||
|
|
|
@ -107,6 +107,13 @@ mod imp {
|
|||
None,
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
glib::ParamSpec::new_boolean(
|
||||
"can-view-media",
|
||||
"Can View Media",
|
||||
"Whether this is a media event that can be viewed",
|
||||
false,
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -146,6 +153,7 @@ mod imp {
|
|||
"show-header" => obj.show_header().to_value(),
|
||||
"can-hide-header" => obj.can_hide_header().to_value(),
|
||||
"time" => obj.time().to_value(),
|
||||
"can-view-media" => obj.can_view_media().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -200,6 +208,7 @@ impl Event {
|
|||
priv_.pure_event.replace(Some(event));
|
||||
|
||||
self.notify("event");
|
||||
self.notify("can-view-media");
|
||||
}
|
||||
|
||||
pub fn matrix_sender(&self) -> UserId {
|
||||
|
@ -506,6 +515,7 @@ impl Event {
|
|||
/// Compatible events:
|
||||
///
|
||||
/// - File message (`MessageType::File`).
|
||||
/// - Image message (`MessageType::Image`).
|
||||
///
|
||||
/// Returns `Ok((filename, binary_content))` on success, `Err` if an error occured while
|
||||
/// fetching the content. Panics on an incompatible event.
|
||||
|
@ -513,11 +523,17 @@ impl Event {
|
|||
if let AnyMessageEventContent::RoomMessage(content) = self.message_content().unwrap() {
|
||||
let client = self.room().session().client();
|
||||
match content.msgtype {
|
||||
MessageType::File(file_content) => {
|
||||
let content = file_content.clone();
|
||||
MessageType::File(content) => {
|
||||
let filename = content.filename.clone().unwrap_or(content.body.clone());
|
||||
let handle = spawn_tokio!(async move { client.get_file(content, true).await });
|
||||
let data = handle.await.unwrap()?.unwrap();
|
||||
return Ok((file_content.filename.unwrap_or(file_content.body), data));
|
||||
return Ok((filename, data));
|
||||
}
|
||||
MessageType::Image(content) => {
|
||||
let filename = content.body.clone();
|
||||
let handle = spawn_tokio!(async move { client.get_file(content, true).await });
|
||||
let data = handle.await.unwrap()?.unwrap();
|
||||
return Ok((filename, data));
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
|
@ -525,4 +541,14 @@ impl Event {
|
|||
|
||||
panic!("Trying to get the media content of an event of incompatible type");
|
||||
}
|
||||
|
||||
/// Whether this is a media event that can be viewed.
|
||||
pub fn can_view_media(&self) -> bool {
|
||||
match self.message_content() {
|
||||
Some(AnyMessageEventContent::RoomMessage(message)) => {
|
||||
matches!(message.msgtype, MessageType::Image(_))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,12 +27,15 @@ impl From<ItemType> for BoxedItemType {
|
|||
}
|
||||
|
||||
mod imp {
|
||||
use std::cell::Cell;
|
||||
|
||||
use super::*;
|
||||
use once_cell::{sync::Lazy, unsync::OnceCell};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Item {
|
||||
pub type_: OnceCell<ItemType>,
|
||||
pub activatable: Cell<bool>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -74,6 +77,13 @@ mod imp {
|
|||
false,
|
||||
glib::ParamFlags::READABLE,
|
||||
),
|
||||
glib::ParamSpec::new_boolean(
|
||||
"activatable",
|
||||
"Activatable",
|
||||
"Whether this item is activatable.",
|
||||
false,
|
||||
glib::ParamFlags::READWRITE,
|
||||
),
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -96,6 +106,7 @@ mod imp {
|
|||
let show_header = value.get().unwrap();
|
||||
let _ = obj.set_show_header(show_header);
|
||||
}
|
||||
"activatable" => self.activatable.set(value.get().unwrap()),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
@ -105,9 +116,19 @@ mod imp {
|
|||
"selectable" => obj.selectable().to_value(),
|
||||
"show-header" => obj.show_header().to_value(),
|
||||
"can-hide-header" => obj.can_hide_header().to_value(),
|
||||
"activatable" => self.activatable.get().to_value(),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
if let Some(event) = obj.event() {
|
||||
event
|
||||
.bind_property("can-view-media", obj, "activatable")
|
||||
.flags(glib::BindingFlags::SYNC_CREATE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,6 +74,17 @@ mod imp {
|
|||
);
|
||||
|
||||
obj.set_default_by_child();
|
||||
|
||||
// Ask for the toggle fullscreen state
|
||||
let fullscreen = gio::SimpleAction::new("toggle-fullscreen", None);
|
||||
fullscreen.connect_activate(clone!(@weak obj as window => move |_, _| {
|
||||
if window.is_fullscreened() {
|
||||
window.unfullscreen();
|
||||
} else {
|
||||
window.fullscreen();
|
||||
}
|
||||
}));
|
||||
obj.add_action(&fullscreen);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue