Browse Source

sidebar: Improve accessibility

merge-requests/1579/head
Kévin Commaille 2 years ago
parent
commit
dcd04598d4
No known key found for this signature in database
GPG Key ID: 29A48C1F03620416
  1. 18
      data/resources/style.css
  2. 1
      po/POTFILES.in
  3. 57
      src/components/context_menu_bin.rs
  4. 5
      src/session/view/content/room_history/item_row.rs
  5. 10
      src/session/view/sidebar/category_row.rs
  6. 9
      src/session/view/sidebar/mod.rs
  7. 26
      src/session/view/sidebar/mod.ui
  8. 184
      src/session/view/sidebar/room_row.rs
  9. 2
      src/session/view/sidebar/room_row.ui
  10. 197
      src/session/view/sidebar/row.rs

18
data/resources/style.css

@ -245,7 +245,7 @@ session-verification .text-button {
background: none;
}
sidebar-row > * {
sidebar-row > *:not(popover) {
margin: 0 6px;
padding: 9px;
border-radius: 6px;
@ -257,29 +257,29 @@ sidebar-row > * {
margin-top: 2px;
}
sidebar-row:focus > * {
sidebar-row:focus > *:not(popover) {
outline-color: alpha(@accent_color, 0.5);
outline-width: 2px;
outline-offset: -2px;
}
sidebar-row:not(.drop-mode) > *:hover {
sidebar-row:not(.drop-mode) > *:not(popover):hover {
background-color: alpha(currentColor, 0.07);
}
.sidebar-list row:active sidebar-row > * {
.sidebar-list row:active sidebar-row > *:not(popover) {
background-color: alpha(currentColor, 0.16);
}
.sidebar-list row:selected sidebar-row > * {
.sidebar-list row:selected sidebar-row > *:not(popover) {
background-color: alpha(currentColor, 0.1);
}
.sidebar-list row:selected sidebar-row:not(.drop-mode) > *:hover {
.sidebar-list row:selected sidebar-row:not(.drop-mode) > *:not(popover):hover {
background-color: alpha(currentColor, 0.13);
}
.sidebar-list row:selected:active sidebar-row > * {
.sidebar-list row:selected:active sidebar-row > *:not(popover) {
background-color: alpha(currentColor, 0.19);
}
@ -331,11 +331,11 @@ sidebar-row.drag > * {
opacity: 0.6;
}
sidebar-row.drop-disabled > * {
sidebar-row.drop-disabled > *:not(popover) {
opacity: 0.6;
}
sidebar-row.drop-empty > * {
sidebar-row.drop-empty > *:not(popover) {
color: @accent_color;
}

1
po/POTFILES.in

@ -117,6 +117,7 @@ src/session/view/media_viewer.ui
src/session/view/room_creation.rs
src/session/view/room_creation.ui
src/session/view/sidebar/category_row.rs
src/session/view/sidebar/mod.rs
src/session/view/sidebar/mod.ui
src/session/view/sidebar/room_row.rs
src/session/view/sidebar/row.rs

57
src/components/context_menu_bin.rs

@ -2,7 +2,7 @@ use adw::subclass::prelude::*;
use gtk::{gdk, glib, glib::clone, prelude::*, CompositeTemplate};
mod imp {
use std::cell::RefCell;
use std::cell::{Cell, RefCell};
use glib::{subclass::InitializingObject, SignalHandlerId};
@ -31,6 +31,11 @@ mod imp {
pub click_gesture: TemplateChild<gtk::GestureClick>,
#[template_child]
pub long_press_gesture: TemplateChild<gtk::GestureLongPress>,
/// Whether this widget has a context menu.
///
/// If this is set to `false`, all the actions will be disabled.
#[property(get, set = Self::set_has_context_menu, explicit_notify)]
pub has_context_menu: Cell<bool>,
/// The popover displaying the context menu.
#[property(get, set = Self::set_popover, explicit_notify, nullable)]
pub popover: RefCell<Option<gtk::PopoverMenu>>,
@ -83,9 +88,11 @@ mod imp {
self.long_press_gesture
.connect_pressed(clone!(@weak obj => move |gesture, x, y| {
gesture.set_state(gtk::EventSequenceState::Claimed);
gesture.reset();
obj.open_menu_at(x as i32, y as i32);
if obj.has_context_menu() {
gesture.set_state(gtk::EventSequenceState::Claimed);
gesture.reset();
obj.open_menu_at(x as i32, y as i32);
}
}));
self.click_gesture.connect_released(
@ -94,8 +101,10 @@ mod imp {
return;
}
gesture.set_state(gtk::EventSequenceState::Claimed);
obj.open_menu_at(x as i32, y as i32);
if obj.has_context_menu() {
gesture.set_state(gtk::EventSequenceState::Claimed);
obj.open_menu_at(x as i32, y as i32);
}
}),
);
self.parent_constructed();
@ -116,6 +125,22 @@ mod imp {
impl BinImpl for ContextMenuBin {}
impl ContextMenuBin {
/// Set whether this widget has a context menu.
fn set_has_context_menu(&self, has_context_menu: bool) {
if self.has_context_menu.get() == has_context_menu {
return;
}
self.has_context_menu.set(has_context_menu);
let obj = self.obj();
obj.update_property(&[gtk::accessible::Property::HasPopup(has_context_menu)]);
obj.action_set_enabled("context-menu.activate", has_context_menu);
obj.action_set_enabled("context-menu.close", has_context_menu);
obj.notify_has_context_menu();
}
/// Set the popover displaying the context menu.
fn set_popover(&self, popover: Option<gtk::PopoverMenu>) {
if *self.popover.borrow() == popover {
@ -150,13 +175,17 @@ mod imp {
}
glib::wrapper! {
/// A Bin widget that adds a context menu.
/// A Bin widget that can have a context menu.
pub struct ContextMenuBin(ObjectSubclass<imp::ContextMenuBin>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
impl ContextMenuBin {
fn open_menu_at(&self, x: i32, y: i32) {
if !self.has_context_menu() {
return;
}
self.menu_opened();
if let Some(popover) = self.popover() {
@ -167,6 +196,12 @@ impl ContextMenuBin {
}
pub trait ContextMenuBinExt: 'static {
/// Whether this widget has a context menu.
fn has_context_menu(&self) -> bool;
/// Set whether this widget has a context menu.
fn set_has_context_menu(&self, has_context_menu: bool);
/// Get the `PopoverMenu` used in the context menu.
fn popover(&self) -> Option<gtk::PopoverMenu>;
@ -178,6 +213,14 @@ pub trait ContextMenuBinExt: 'static {
}
impl<O: IsA<ContextMenuBin>> ContextMenuBinExt for O {
fn has_context_menu(&self) -> bool {
self.upcast_ref().has_context_menu()
}
fn set_has_context_menu(&self, has_context_menu: bool) {
self.upcast_ref().set_has_context_menu(has_context_menu);
}
fn popover(&self) -> Option<gtk::PopoverMenu> {
self.upcast_ref().popover()
}

5
src/session/view/content/room_history/item_row.rs

@ -57,8 +57,11 @@ mod imp {
impl ObjectImpl for ItemRow {
fn constructed(&self) {
self.parent_constructed();
let obj = self.obj();
obj.set_has_context_menu(true);
self.obj().connect_parent_notify(|obj| {
obj.connect_parent_notify(|obj| {
obj.update_highlight();
});
}

10
src/session/view/sidebar/category_row.rs

@ -57,7 +57,15 @@ mod imp {
}
#[glib::derived_properties]
impl ObjectImpl for CategoryRow {}
impl ObjectImpl for CategoryRow {
fn constructed(&self) {
self.parent_constructed();
self.obj().connect_parent_notify(|obj| {
obj.set_expanded_accessibility_state(obj.expanded());
});
}
}
impl WidgetImpl for CategoryRow {}
impl BinImpl for CategoryRow {}

9
src/session/view/sidebar/mod.rs

@ -5,6 +5,7 @@ mod row;
mod verification_row;
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gio, glib, glib::clone, CompositeTemplate};
use tracing::error;
@ -288,14 +289,18 @@ impl Sidebar {
self.notify_drop_active_target_category_type();
}
/// The shared popover for a room row in the sidebar.
pub fn room_row_popover(&self) -> &gtk::PopoverMenu {
let imp = self.imp();
imp.room_row_popover.get_or_init(|| {
gtk::PopoverMenu::builder()
let popover = gtk::PopoverMenu::builder()
.menu_model(&*imp.room_row_menu)
.has_arrow(false)
.halign(gtk::Align::Start)
.build()
.build();
popover.update_property(&[gtk::accessible::Property::Label(&gettext("Context Menu"))]);
popover
})
}
}

26
src/session/view/sidebar/mod.ui

@ -31,51 +31,51 @@
<item>
<attribute name="label" translatable="yes">_Accept</attribute>
<attribute name="action">room-row.accept-invite</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Reject</attribute>
<attribute name="action">room-row.reject-invite</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Move to _Favorites</attribute>
<attribute name="action">room-row.set-favorite</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Move to _Rooms</attribute>
<attribute name="action">room-row.set-normal</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Move to Low _Priority</attribute>
<attribute name="action">room-row.set-lowpriority</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">_Leave Room</attribute>
<attribute name="action">room-row.leave</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">Re_join Room</attribute>
<attribute name="action">room-row.join</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Forget Room</attribute>
<attribute name="action">room-row.forget</attribute>
<attribute name="hidden-when">action-disabled</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
</section>
</menu>
<template class="Sidebar" parent="AdwNavigationPage">
<property name="title">Sidebar</property><!-- This is not displayed so no need to make it translatable -->
<property name="title" translatable="yes">Sidebar</property>
<child>
<object class="AdwToolbarView">
<child type="top">
@ -90,9 +90,6 @@
<property name="menu-model">primary_menu</property>
<property name="primary">True</property>
<property name="tooltip-text" translatable="yes">Main Menu</property>
<accessibility>
<property name="label" translatable="yes">Main Menu</property>
</accessibility>
</object>
</child>
<child type="end">
@ -101,9 +98,6 @@
<property name="active" bind-source="room_search" bind-property="search-mode-enabled" bind-flags="sync-create"/>
<property name="action-name">session.toggle-room-search</property>
<property name="tooltip-text" translatable="yes">Toggle Room Search</property>
<accessibility>
<property name="label" translatable="yes">Toggle Room Search</property>
</accessibility>
</object>
</child>
</object>
@ -142,7 +136,7 @@
<property name="single-click-activate">true</property>
<property name="tab-behavior">item</property>
<accessibility>
<property name="label" translatable="yes">Sidebar</property>
<property name="label" translatable="yes">Room List</property>
<property name="description" translatable="yes">Allows to navigate between rooms</property>
</accessibility>
</object>

184
src/session/view/sidebar/room_row.rs

@ -1,14 +1,11 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{gdk, glib, glib::clone, CompositeTemplate};
use super::Row;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl},
i18n::gettext_f,
session::model::{HighlightFlags, Room, RoomType},
toast,
utils::{message_dialog, BoundObject},
utils::BoundObject,
};
mod imp {
@ -39,40 +36,13 @@ mod imp {
impl ObjectSubclass for RoomRow {
const NAME: &'static str = "SidebarRoomRow";
type Type = super::RoomRow;
type ParentType = ContextMenuBin;
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
klass.set_css_name("room");
klass.set_accessible_role(gtk::AccessibleRole::Group);
klass.install_action_async("room-row.accept-invite", None, |obj, _, _| async move {
obj.set_category(RoomType::Normal).await;
});
klass.install_action_async("room-row.reject-invite", None, |obj, _, _| async move {
obj.set_category(RoomType::Left).await;
});
klass.install_action_async("room-row.set-favorite", None, |obj, _, _| async move {
obj.set_category(RoomType::Favorite).await;
});
klass.install_action_async("room-row.set-normal", None, |obj, _, _| async move {
obj.set_category(RoomType::Normal).await;
});
klass.install_action_async("room-row.set-lowpriority", None, |obj, _, _| async move {
obj.set_category(RoomType::LowPriority).await;
});
klass.install_action_async("room-row.leave", None, |obj, _, _| async move {
obj.set_category(RoomType::Left).await;
});
klass.install_action_async("room-row.join", None, |obj, _, _| async move {
obj.set_category(RoomType::Normal).await;
});
klass.install_action_async("room-row.forget", None, |obj, _, _| async move {
obj.forget().await;
});
}
fn instance_init(obj: &InitializingObject<Self>) {
@ -114,21 +84,6 @@ mod imp {
impl WidgetImpl for RoomRow {}
impl BinImpl for RoomRow {}
impl ContextMenuBinImpl for RoomRow {
fn menu_opened(&self) {
let obj = self.obj();
if let Some(sidebar) = obj
.parent()
.and_downcast_ref::<Row>()
.and_then(|row| row.sidebar())
{
let popover = sidebar.room_row_popover();
obj.set_popover(Some(popover.clone()));
}
}
}
impl RoomRow {
/// Set the room represented by this row.
pub fn set_room(&self, room: Option<Room>) {
@ -165,31 +120,19 @@ mod imp {
let name_handler = room.connect_display_name_notify(clone!(@weak obj => move |_| {
obj.update_accessibility_label();
}));
let join_rule_handler =
room.connect_join_rule_changed(clone!(@weak obj => move |_| {
obj.update_actions()
}));
if room.category() == RoomType::Left {
self.display_name.add_css_class("dim-label");
}
self.room.set(
room,
vec![
highlight_handler,
direct_handler,
name_handler,
join_rule_handler,
],
);
self.room
.set(room, vec![highlight_handler, direct_handler, name_handler]);
obj.update_accessibility_label();
}
obj.update_highlight();
obj.update_direct_icon();
obj.update_actions();
obj.notify_room();
}
}
@ -198,7 +141,7 @@ mod imp {
glib::wrapper! {
/// A sidebar row representing a room.
pub struct RoomRow(ObjectSubclass<imp::RoomRow>)
@extends gtk::Widget, adw::Bin, ContextMenuBin, @implements gtk::Accessible;
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
impl RoomRow {
@ -228,79 +171,6 @@ impl RoomRow {
}
}
/// Enable or disable actions according to the category of the room.
fn update_actions(&self) {
if let Some(room) = self.room() {
match room.category() {
RoomType::Invited => {
self.action_set_enabled("room-row.accept-invite", true);
self.action_set_enabled("room-row.reject-invite", true);
self.action_set_enabled("room-row.set-favorite", false);
self.action_set_enabled("room-row.set-normal", false);
self.action_set_enabled("room-row.set-lowpriority", false);
self.action_set_enabled("room-row.leave", false);
self.action_set_enabled("room-row.join", false);
self.action_set_enabled("room-row.forget", false);
return;
}
RoomType::Favorite => {
self.action_set_enabled("room-row.accept-invite", false);
self.action_set_enabled("room-row.reject-invite", false);
self.action_set_enabled("room-row.set-favorite", false);
self.action_set_enabled("room-row.set-normal", true);
self.action_set_enabled("room-row.set-lowpriority", true);
self.action_set_enabled("room-row.leave", true);
self.action_set_enabled("room-row.join", false);
self.action_set_enabled("room-row.forget", false);
return;
}
RoomType::Normal => {
self.action_set_enabled("room-row.accept-invite", false);
self.action_set_enabled("room-row.reject-invite", false);
self.action_set_enabled("room-row.set-favorite", true);
self.action_set_enabled("room-row.set-normal", false);
self.action_set_enabled("room-row.set-lowpriority", true);
self.action_set_enabled("room-row.leave", true);
self.action_set_enabled("room-row.join", false);
self.action_set_enabled("room-row.forget", false);
return;
}
RoomType::LowPriority => {
self.action_set_enabled("room-row.accept-invite", false);
self.action_set_enabled("room-row.reject-invite", false);
self.action_set_enabled("room-row.set-favorite", true);
self.action_set_enabled("room-row.set-normal", true);
self.action_set_enabled("room-row.set-lowpriority", false);
self.action_set_enabled("room-row.leave", true);
self.action_set_enabled("room-row.join", false);
self.action_set_enabled("room-row.forget", false);
return;
}
RoomType::Left => {
self.action_set_enabled("room-row.accept-invite", false);
self.action_set_enabled("room-row.reject-invite", false);
self.action_set_enabled("room-row.set-favorite", false);
self.action_set_enabled("room-row.set-normal", false);
self.action_set_enabled("room-row.set-lowpriority", false);
self.action_set_enabled("room-row.leave", false);
self.action_set_enabled("room-row.join", room.can_join());
self.action_set_enabled("room-row.forget", true);
return;
}
RoomType::Outdated | RoomType::Space | RoomType::Ignored => {}
}
}
self.action_set_enabled("room-row.accept-invite", false);
self.action_set_enabled("room-row.reject-invite", false);
self.action_set_enabled("room-row.set-favorite", false);
self.action_set_enabled("room-row.set-normal", false);
self.action_set_enabled("room-row.set-lowpriority", false);
self.action_set_enabled("room-row.leave", false);
self.action_set_enabled("room-row.join", false);
self.action_set_enabled("room-row.forget", false);
}
fn drag_prepare(&self, drag: &gtk::DragSource, x: f64, y: f64) -> Option<gdk::ContentProvider> {
let room = self.room()?;
@ -340,50 +210,6 @@ impl RoomRow {
row.remove_css_class("drag");
}
/// Change the category of this room.
async fn set_category(&self, category: RoomType) {
let Some(window) = self.root().and_downcast::<gtk::Window>() else {
return;
};
let Some(room) = self.room() else {
return;
};
let previous_category = room.category();
if category == RoomType::Left && !message_dialog::confirm_leave_room(&room, &window).await {
return;
}
if room.set_category(category).await.is_err() {
toast!(
self,
gettext(
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
"Failed to move {room} from {previous_category} to {new_category}.",
),
@room,
previous_category = previous_category.to_string(),
new_category = category.to_string(),
);
}
}
/// Forget this room.
async fn forget(&self) {
let Some(room) = self.room() else {
return;
};
if room.forget().await.is_err() {
toast!(
self,
// Translators: Do NOT translate the content between '{' and '}', this is a variable name.
gettext("Failed to forget {room}."),
@room,
);
}
}
fn update_direct_icon(&self) {
let imp = self.imp();
let is_direct = self.room().is_some_and(|room| room.is_direct());

2
src/session/view/sidebar/room_row.ui

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="SidebarRoomRow" parent="ContextMenuBin">
<template class="SidebarRoomRow" parent="AdwBin">
<child>
<object class="GtkBox">
<binding name="tooltip-text">

197
src/session/view/sidebar/row.rs

@ -1,9 +1,10 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::{accessible::Relation, gdk, glib, glib::clone};
use gtk::{accessible::Relation, gdk, gio, glib, glib::clone};
use super::{CategoryRow, IconItemRow, RoomRow, Sidebar, VerificationRow};
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl},
session::model::{
Category, CategoryType, IdentityVerification, Room, RoomType, SidebarIconItem,
SidebarIconItemType, SidebarItem,
@ -30,13 +31,14 @@ mod imp {
#[property(get = Self::item)]
pub item: PhantomData<Option<SidebarItem>>,
pub bindings: RefCell<Vec<glib::Binding>>,
room_join_rule_handler: RefCell<Option<glib::SignalHandlerId>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Row {
const NAME: &'static str = "SidebarRow";
type Type = super::Row;
type ParentType = adw::Bin;
type ParentType = ContextMenuBin;
fn class_init(klass: &mut Self::Class) {
klass.set_css_name("sidebar-row");
@ -68,11 +70,34 @@ mod imp {
);
obj.add_controller(drop);
}
fn dispose(&self) {
if let Some(room) = self.room() {
if let Some(handler) = self.room_join_rule_handler.take() {
room.disconnect(handler);
}
}
}
}
impl WidgetImpl for Row {}
impl BinImpl for Row {}
impl ContextMenuBinImpl for Row {
fn menu_opened(&self) {
if !self.item().is_some_and(|i| i.is::<Room>()) {
// No context menu.
return;
}
let obj = self.obj();
if let Some(sidebar) = obj.sidebar() {
let popover = sidebar.room_row_popover();
obj.set_popover(Some(popover.clone()));
}
}
}
impl Row {
/// Set the ancestor sidebar of this row.
fn set_sidebar(&self, sidebar: Sidebar) {
@ -104,9 +129,16 @@ mod imp {
for binding in self.bindings.take() {
binding.unbind();
}
if let Some(room) = self.room() {
if let Some(handler) = self.room_join_rule_handler.take() {
room.disconnect(handler);
}
}
self.list_row.replace(list_row.clone());
self.update_context_menu();
let mut bindings = vec![];
if let Some((row, item)) = list_row.zip(self.item()) {
if let Some(category) = item.downcast_ref::<Category>() {
@ -134,6 +166,13 @@ mod imp {
child
};
let room_join_rule_handler =
room.connect_join_rule_changed(clone!(@weak self as imp => move |_| {
imp.update_context_menu();
}));
self.room_join_rule_handler
.replace(Some(room_join_rule_handler));
child.set_room(Some(room.clone()));
} else if let Some(icon_item) = item.downcast_ref::<SidebarIconItem>() {
let child = if let Some(child) = obj.child().and_downcast::<IconItemRow>() {
@ -164,6 +203,7 @@ mod imp {
self.bindings.replace(bindings);
self.update_context_menu();
obj.notify_item();
obj.notify_list_row();
}
@ -176,13 +216,159 @@ mod imp {
.and_then(|r| r.item())
.and_downcast()
}
/// Get the `Room` of this item, if this is a room row.
pub(super) fn room(&self) -> Option<Room> {
self.item().and_downcast()
}
/// Whether this has a room context menu.
fn has_room_context_menu(&self) -> bool {
self.room().is_some_and(|r| {
matches!(
r.category(),
RoomType::Invited
| RoomType::Favorite
| RoomType::Normal
| RoomType::LowPriority
| RoomType::Left
)
})
}
/// Update the context menu according to the current state.
fn update_context_menu(&self) {
let obj = self.obj();
if !self.has_room_context_menu() {
obj.insert_action_group("room-row", None::<&gio::ActionGroup>);
obj.set_has_context_menu(false);
return;
}
obj.insert_action_group("room-row", self.room_actions().as_ref());
obj.set_has_context_menu(true);
}
/// An action group with the available room actions.
fn room_actions(&self) -> Option<gio::SimpleActionGroup> {
let Some(room) = self.room() else {
return None;
};
let obj = self.obj();
let action_group = gio::SimpleActionGroup::new();
let category = room.category();
match category {
RoomType::Invited => {
action_group.add_action_entries([
gio::ActionEntry::builder("accept-invite")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Normal).await;
}));
}
}))
.build(),
gio::ActionEntry::builder("reject-invite")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Left).await;
}));
}
}))
.build(),
]);
}
RoomType::Favorite | RoomType::Normal | RoomType::LowPriority => {
if matches!(category, RoomType::Favorite | RoomType::LowPriority) {
action_group.add_action_entries([gio::ActionEntry::builder("set-normal")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Normal).await;
}));
}
}))
.build()]);
}
if matches!(category, RoomType::Normal | RoomType::LowPriority) {
action_group.add_action_entries([gio::ActionEntry::builder(
"set-favorite",
)
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Favorite).await;
}));
}
}))
.build()]);
}
if matches!(category, RoomType::Favorite | RoomType::Normal) {
action_group.add_action_entries([gio::ActionEntry::builder(
"set-lowpriority",
)
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::LowPriority).await;
}));
}
}))
.build()]);
}
action_group.add_action_entries([gio::ActionEntry::builder("leave")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Left).await;
}));
}
}))
.build()]);
}
RoomType::Left => {
if room.can_join() {
action_group.add_action_entries([gio::ActionEntry::builder("join")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.set_room_category(&room, RoomType::Normal).await;
}));
}
}))
.build()]);
}
action_group.add_action_entries([gio::ActionEntry::builder("forget")
.activate(clone!(@weak obj => move |_, _, _| {
if let Some(room) = obj.room() {
spawn!(clone!(@weak obj, @weak room => async move {
obj.forget_room(&room).await;
}));
}
}))
.build()]);
}
RoomType::Outdated | RoomType::Space | RoomType::Ignored => {}
}
Some(action_group)
}
}
}
glib::wrapper! {
/// A row of the sidebar.
pub struct Row(ObjectSubclass<imp::Row>)
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
@extends gtk::Widget, adw::Bin, ContextMenuBin, @implements gtk::Accessible;
}
impl Row {
@ -193,6 +379,11 @@ impl Row {
.build()
}
/// Get the `Room` of this item, if this is a room row.
pub fn room(&self) -> Option<Room> {
self.imp().room()
}
/// Get the `RoomType` of this item.
///
/// If this is not a `Category` or one of its children, returns `None`.

Loading…
Cancel
Save