Browse Source

utils: Add extension trait for reusing child widget

There would be less duplication if Rust allowed generic implementations
with different bounds. Oh well…
merge-requests/2003/head
Kévin Commaille 11 months ago
parent
commit
426b99ec73
No known key found for this signature in database
GPG Key ID: C971D9DBC9D678D
  1. 6
      src/components/label_with_widgets.rs
  2. 12
      src/components/loading/bin.rs
  3. 59
      src/components/loading/button.rs
  4. 9
      src/components/media/content_viewer.rs
  5. 6
      src/components/media/location_viewer.rs
  6. 2
      src/prelude.rs
  7. 12
      src/session/view/content/explore/mod.rs
  8. 6
      src/session/view/content/explore/public_room_row.rs
  9. 12
      src/session/view/content/room_details/history_viewer/audio.rs
  10. 6
      src/session/view/content/room_details/history_viewer/audio_row.rs
  11. 12
      src/session/view/content/room_details/history_viewer/file.rs
  12. 6
      src/session/view/content/room_details/history_viewer/file_row.rs
  13. 16
      src/session/view/content/room_details/history_viewer/visual_media.rs
  14. 6
      src/session/view/content/room_details/history_viewer/visual_media_item.rs
  15. 2
      src/session/view/content/room_details/history_viewer/visual_media_item.ui
  16. 34
      src/session/view/content/room_details/members_page/members_list_view/item_row.rs
  17. 6
      src/session/view/content/room_history/divider_row.rs
  18. 26
      src/session/view/content/room_history/event_row.rs
  19. 12
      src/session/view/content/room_history/message_row/caption.rs
  20. 75
      src/session/view/content/room_history/message_row/content.rs
  21. 6
      src/session/view/content/room_history/message_row/mod.rs
  22. 18
      src/session/view/content/room_history/message_row/text/mod.rs
  23. 33
      src/session/view/content/room_history/mod.rs
  24. 43
      src/session/view/content/room_history/state/content.rs
  25. 6
      src/session/view/content/room_history/state/group_row.rs
  26. 6
      src/session/view/content/room_history/state/row.rs
  27. 6
      src/session/view/content/room_history/typing_row.rs
  28. 6
      src/session/view/sidebar/icon_item_row.rs
  29. 6
      src/session/view/sidebar/room_row.rs
  30. 46
      src/session/view/sidebar/row.rs
  31. 6
      src/session/view/sidebar/verification_row.rs
  32. 74
      src/utils/mod.rs

6
src/components/label_with_widgets.rs

@ -338,3 +338,9 @@ impl LabelWithWidgets {
self.imp().set_label_and_widgets(label, widgets);
}
}
impl Default for LabelWithWidgets {
fn default() -> Self {
Self::new()
}
}

12
src/components/loading/bin.rs

@ -1,6 +1,8 @@
use adw::prelude::*;
use gtk::{glib, subclass::prelude::*, CompositeTemplate};
use crate::utils::ChildPropertyExt;
mod imp {
use std::marker::PhantomData;
@ -118,3 +120,13 @@ impl Default for LoadingBin {
Self::new()
}
}
impl ChildPropertyExt for LoadingBin {
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child.map(Cast::upcast_ref));
}
}

59
src/components/loading/button.rs

@ -1,7 +1,8 @@
use adw::subclass::prelude::*;
use gtk::{glib, pango, prelude::*};
use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, pango};
use super::LoadingBin;
use crate::prelude::*;
mod imp {
use std::marker::PhantomData;
@ -65,26 +66,22 @@ mod imp {
}
let obj = self.obj();
let child_label =
if let Some(child_label) = self.loading_bin.child().and_downcast::<gtk::Label>() {
child_label
} else {
let child_label = gtk::Label::builder()
.ellipsize(pango::EllipsizeMode::End)
.use_underline(true)
.mnemonic_widget(&*obj)
.css_classes(["text-button"])
.build();
self.loading_bin.set_child(Some(child_label.clone()));
// In case it was an image before.
obj.remove_css_class("image-button");
obj.update_relation(&[gtk::accessible::Relation::LabelledBy(&[
child_label.upcast_ref()
])]);
child_label
};
let child_label = self.loading_bin.child_or_else::<gtk::Label>(|| {
let child_label = gtk::Label::builder()
.ellipsize(pango::EllipsizeMode::End)
.use_underline(true)
.mnemonic_widget(&*obj)
.css_classes(["text-button"])
.build();
// In case it was an image before.
obj.remove_css_class("image-button");
obj.update_relation(&[gtk::accessible::Relation::LabelledBy(&[
child_label.upcast_ref()
])]);
child_label
});
child_label.set_label(label);
@ -106,19 +103,13 @@ mod imp {
}
let obj = self.obj();
let child_image =
if let Some(child_image) = self.loading_bin.child().and_downcast::<gtk::Image>() {
child_image
} else {
let child_image = gtk::Image::builder()
.accessible_role(gtk::AccessibleRole::Presentation)
.build();
self.loading_bin.set_child(Some(child_image.clone()));
obj.add_css_class("image-button");
let child_image = self.loading_bin.child_or_else::<gtk::Image>(|| {
obj.add_css_class("image-button");
child_image
};
gtk::Image::builder()
.accessible_role(gtk::AccessibleRole::Presentation)
.build()
});
child_image.set_icon_name(Some(icon_name));

9
src/components/media/content_viewer.rs

@ -230,14 +230,7 @@ mod imp {
/// View the given location as a geo URI.
pub(super) fn view_location(&self, geo_uri: &GeoUri) {
let location =
if let Some(location) = self.viewer.child().and_downcast::<LocationViewer>() {
location
} else {
let location = LocationViewer::new();
self.viewer.set_child(Some(&location));
location
};
let location = self.viewer.child_or_default::<LocationViewer>();
location.set_location(geo_uri);
self.update_animated_paintable_state();

6
src/components/media/location_viewer.rs

@ -148,3 +148,9 @@ impl LocationViewer {
self.imp().set_location(geo_uri);
}
}
impl Default for LocationViewer {
fn default() -> Self {
Self::new()
}
}

2
src/prelude.rs

@ -10,6 +10,6 @@ pub(crate) use crate::{
utils::{
matrix::ext_traits::*,
string::{StrExt, StrMutExt},
LocationExt,
ChildPropertyExt, IsABin, LocationExt,
},
};

12
src/session/view/content/explore/mod.rs

@ -17,6 +17,7 @@ pub use self::{
use self::{server::ExploreServer, server_list::ExploreServerList, server_row::ExploreServerRow};
use crate::{
components::LoadingRow,
prelude::*,
session::model::Session,
utils::{BoundObject, LoadingState, SingleItemListModel},
};
@ -122,16 +123,7 @@ mod imp {
};
if let Some(public_room) = item.downcast_ref::<PublicRoom>() {
let public_room_row = if let Some(public_room_row) =
list_item.child().and_downcast::<PublicRoomRow>()
{
public_room_row
} else {
let public_room_row = PublicRoomRow::new();
list_item.set_child(Some(&public_room_row));
public_room_row
};
let public_room_row = list_item.child_or_default::<PublicRoomRow>();
public_room_row.set_public_room(public_room);
} else if let Some(loading_row) = item.downcast_ref::<LoadingRow>() {
list_item.set_child(Some(loading_row));

6
src/session/view/content/explore/public_room_row.rs

@ -240,3 +240,9 @@ impl PublicRoomRow {
glib::Object::new()
}
}
impl Default for PublicRoomRow {
fn default() -> Self {
Self::new()
}
}

12
src/session/view/content/room_details/history_viewer/audio.rs

@ -5,6 +5,7 @@ use tracing::error;
use super::{AudioRow, HistoryViewerEvent, HistoryViewerEventType, HistoryViewerTimeline};
use crate::{
components::LoadingRow,
prelude::*,
spawn,
utils::{BoundConstructOnlyObject, LoadingState},
};
@ -86,16 +87,7 @@ mod imp {
list_item.set_child(Some(loading_row));
} else if let Some(event) = item.and_downcast::<HistoryViewerEvent>() {
let audio_row =
if let Some(audio_row) = list_item.child().and_downcast::<AudioRow>() {
audio_row
} else {
let audio_row = AudioRow::new();
list_item.set_child(Some(&audio_row));
audio_row
};
let audio_row = list_item.child_or_default::<AudioRow>();
audio_row.set_event(Some(event));
}
});

6
src/session/view/content/room_details/history_viewer/audio_row.rs

@ -191,3 +191,9 @@ impl AudioRow {
glib::Object::new()
}
}
impl Default for AudioRow {
fn default() -> Self {
Self::new()
}
}

12
src/session/view/content/room_details/history_viewer/file.rs

@ -5,6 +5,7 @@ use tracing::error;
use super::{FileRow, HistoryViewerEvent, HistoryViewerEventType, HistoryViewerTimeline};
use crate::{
components::LoadingRow,
prelude::*,
spawn,
utils::{BoundConstructOnlyObject, LoadingState},
};
@ -86,16 +87,7 @@ mod imp {
list_item.set_child(Some(loading_row));
} else if let Some(event) = item.and_downcast::<HistoryViewerEvent>() {
let file_row =
if let Some(file_row) = list_item.child().and_downcast::<FileRow>() {
file_row
} else {
let file_row = FileRow::new();
list_item.set_child(Some(&file_row));
file_row
};
let file_row = list_item.child_or_default::<FileRow>();
file_row.set_event(Some(event));
}
});

6
src/session/view/content/room_details/history_viewer/file_row.rs

@ -177,3 +177,9 @@ impl FileRow {
glib::Object::new()
}
}
impl Default for FileRow {
fn default() -> Self {
Self::new()
}
}

16
src/session/view/content/room_details/history_viewer/visual_media.rs

@ -5,6 +5,7 @@ use tracing::error;
use super::{HistoryViewerEvent, HistoryViewerEventType, HistoryViewerTimeline, VisualMediaItem};
use crate::{
components::LoadingRow,
prelude::*,
session::view::MediaViewer,
spawn,
utils::{BoundConstructOnlyObject, LoadingState},
@ -91,20 +92,7 @@ mod imp {
list_item.set_child(Some(loading_row));
} else if let Some(event) = item.and_downcast::<HistoryViewerEvent>() {
let media_item = if let Some(media_item) =
list_item.child().and_downcast::<VisualMediaItem>()
{
media_item
} else {
let media_item = VisualMediaItem::new();
media_item.set_width_request(SIZE_REQUEST);
media_item.set_height_request(SIZE_REQUEST);
list_item.set_child(Some(&media_item));
media_item
};
let media_item = list_item.child_or_default::<VisualMediaItem>();
media_item.set_event(Some(event));
}
});

6
src/session/view/content/room_details/history_viewer/visual_media_item.rs

@ -241,3 +241,9 @@ impl VisualMediaItem {
glib::Object::new()
}
}
impl Default for VisualMediaItem {
fn default() -> Self {
Self::new()
}
}

2
src/session/view/content/room_details/history_viewer/visual_media_item.ui

@ -3,6 +3,8 @@
<template class="ContentVisualMediaHistoryViewerItem" parent="GtkWidget">
<property name="focusable">True</property>
<property name="accessible-role">button</property>
<property name="height-request">150</property>
<property name="width-request">150</property>
<child>
<object class="GtkGestureClick">
<signal name="released" handler="activate" swapped="true"/>

34
src/session/view/content/room_details/members_page/members_list_view/item_row.rs

@ -2,9 +2,12 @@ use adw::{prelude::*, subclass::prelude::*};
use gtk::glib;
use super::MembershipSubpageRow;
use crate::session::{
model::Member,
view::content::room_details::{MemberRow, MembershipSubpageItem},
use crate::{
prelude::*,
session::{
model::Member,
view::content::room_details::{MemberRow, MembershipSubpageItem},
},
};
mod imp {
@ -50,26 +53,15 @@ mod imp {
if let Some(item) = &item {
if let Some(member) = item.downcast_ref::<Member>() {
let child = if let Some(child) = obj.child().and_downcast::<MemberRow>() {
child
} else {
let child = MemberRow::new(true);
obj.set_child(Some(&child));
child
};
let child = obj.child_or_else::<MemberRow>(|| MemberRow::new(true));
child.set_member(Some(member.clone()));
self.set_activatable(true);
} else if let Some(item) = item.downcast_ref::<MembershipSubpageItem>() {
let child =
if let Some(child) = obj.child().and_downcast::<MembershipSubpageRow>() {
child
} else {
let child = MembershipSubpageRow::new();
child.set_activatable(false);
obj.set_child(Some(&child));
child
};
let child = obj.child_or_else::<MembershipSubpageRow>(|| {
let child = MembershipSubpageRow::new();
child.set_activatable(false);
child
});
child.set_item(Some(item.clone()));
self.set_activatable(true);
@ -108,3 +100,5 @@ impl ItemRow {
glib::Object::new()
}
}
impl IsABin for ItemRow {}

6
src/session/view/content/room_history/divider_row.rs

@ -139,3 +139,9 @@ impl DividerRow {
glib::Object::new()
}
}
impl Default for DividerRow {
fn default() -> Self {
Self::new()
}
}

26
src/session/view/content/room_history/event_row.rs

@ -292,22 +292,10 @@ mod imp {
let obj = self.obj();
if event.is_state_event() {
let child = if let Some(child) = obj.child().and_downcast::<StateRow>() {
child
} else {
let child = StateRow::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<StateRow>();
child.set_event(event);
} else {
let child = if let Some(child) = obj.child().and_downcast::<MessageRow>() {
child
} else {
let child = MessageRow::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<MessageRow>();
child.set_event(event);
}
}
@ -365,3 +353,13 @@ impl EventRow {
.build()
}
}
impl ChildPropertyExt for EventRow {
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child);
}
}

12
src/session/view/content/room_history/message_row/caption.rs

@ -2,7 +2,7 @@ use gtk::{glib, prelude::*, subclass::prelude::*};
use ruma::events::room::message::FormattedBody;
use super::{text::MessageText, ContentFormat};
use crate::session::model::Room;
use crate::{prelude::*, session::model::Room};
mod imp {
use std::marker::PhantomData;
@ -121,3 +121,13 @@ impl Default for MessageCaption {
Self::new()
}
}
impl ChildPropertyExt for MessageCaption {
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child.map(Cast::upcast_ref));
}
}

75
src/session/view/content/room_history/message_row/content.rs

@ -216,28 +216,10 @@ impl MessageContent {
}
}
/// Helper trait for types used to build a message's content.
trait MessageContentContainer: IsA<gtk::Widget> {
/// Get the child of this widget
fn child(&self) -> Option<gtk::Widget>;
/// Set the child of this widget
fn set_child(&self, child: Option<gtk::Widget>);
/// Reuse the child of this widget if it is of the correct type `W`, or
/// replace it with a new `W` constructed with its `Default` implementation.
///
/// Returns the reused or new widget.
fn reuse_child_or_default<W: IsA<gtk::Widget> + Clone + Default>(&self) -> W {
if let Some(child) = self.child().and_downcast::<W>() {
child
} else {
let child = W::default();
self.set_child(Some(child.clone().upcast()));
child
}
}
impl IsABin for MessageContent {}
/// Helper trait for types used to build a message's content.
trait MessageContentContainer: ChildPropertyExt {
/// Build the content widget of `event` as a child of this widget.
fn build_content(
&self,
@ -279,7 +261,7 @@ trait MessageContentContainer: IsA<gtk::Widget> {
);
}
MsgLikeKind::UnableToDecrypt(_) => {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_plain_text(gettext("Could not decrypt this message, decryption will be retried once the keys are available."), format);
}
msg_like_kind => {
@ -296,7 +278,7 @@ trait MessageContentContainer: IsA<gtk::Widget> {
/// Build the widget of an unsupported content as a child of this widget.
fn build_unsupported_content(&self, format: ContentFormat) {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_plain_text(gettext("Unsupported event"), format);
}
@ -324,7 +306,7 @@ trait MessageContentContainer: IsA<gtk::Widget> {
match msgtype {
MessageType::Emote(message) => {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_emote(
message.formatted.clone(),
message.body.clone(),
@ -335,11 +317,11 @@ trait MessageContentContainer: IsA<gtk::Widget> {
);
}
MessageType::Location(message) => {
let child = self.reuse_child_or_default::<MessageLocation>();
let child = self.child_or_default::<MessageLocation>();
child.set_geo_uri(&message.geo_uri, format);
}
MessageType::Notice(message) => {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_markup(
message.formatted.clone(),
message.body.clone(),
@ -349,11 +331,11 @@ trait MessageContentContainer: IsA<gtk::Widget> {
);
}
MessageType::ServerNotice(message) => {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_plain_text(message.body.clone(), format);
}
MessageType::Text(message) => {
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_markup(
message.formatted.clone(),
message.body.clone(),
@ -364,7 +346,7 @@ trait MessageContentContainer: IsA<gtk::Widget> {
}
msgtype => {
warn!("Event not supported: {msgtype:?}");
let child = self.reuse_child_or_default::<MessageText>();
let child = self.child_or_default::<MessageText>();
child.with_plain_text(gettext("Unsupported event"), format);
}
}
@ -381,7 +363,7 @@ trait MessageContentContainer: IsA<gtk::Widget> {
cache_key: MessageCacheKey,
) {
if let Some((caption, formatted_caption)) = media_message.caption() {
let caption_widget = self.reuse_child_or_default::<MessageCaption>();
let caption_widget = self.child_or_default::<MessageCaption>();
caption_widget.set_caption(
caption.to_owned(),
@ -413,54 +395,35 @@ trait MessageContentContainer: IsA<gtk::Widget> {
let Some(session) = room.session() else {
return;
};
let widget = self.reuse_child_or_default::<MessageAudio>();
let widget = self.child_or_default::<MessageAudio>();
widget.audio(audio.into(), &session, format, cache_key);
}
MediaMessage::File(file) => {
let widget = self.reuse_child_or_default::<MessageFile>();
let widget = self.child_or_default::<MessageFile>();
let media_message = MediaMessage::from(file);
widget.set_filename(Some(media_message.filename()));
widget.set_format(format);
}
MediaMessage::Image(image) => {
let widget = self.reuse_child_or_default::<MessageVisualMedia>();
let widget = self.child_or_default::<MessageVisualMedia>();
widget.set_media_message(image.into(), room, format, cache_key);
}
MediaMessage::Video(video) => {
let widget = self.reuse_child_or_default::<MessageVisualMedia>();
let widget = self.child_or_default::<MessageVisualMedia>();
widget.set_media_message(video.into(), room, format, cache_key);
}
MediaMessage::Sticker(sticker) => {
let widget = self.reuse_child_or_default::<MessageVisualMedia>();
let widget = self.child_or_default::<MessageVisualMedia>();
widget.set_media_message(sticker.into(), room, format, cache_key);
}
}
}
}
impl<W> MessageContentContainer for W
where
W: IsA<adw::Bin> + IsA<gtk::Widget>,
{
fn child(&self) -> Option<gtk::Widget> {
BinExt::child(self)
}
fn set_child(&self, child: Option<gtk::Widget>) {
BinExt::set_child(self, child.as_ref());
}
}
impl<W> MessageContentContainer for W where W: IsABin {}
impl MessageContentContainer for MessageCaption {
fn child(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child(&self, child: Option<gtk::Widget>) {
self.set_child(child);
}
}
impl MessageContentContainer for MessageCaption {}
/// The data used as a cache key for messages.
///

6
src/session/view/content/room_history/message_row/mod.rs

@ -287,3 +287,9 @@ impl MessageRow {
self.imp().texture()
}
}
impl Default for MessageRow {
fn default() -> Self {
Self::new()
}
}

18
src/session/view/content/room_history/message_row/text/mod.rs

@ -214,13 +214,7 @@ mod imp {
fn build_plain_text(&self, mut text: String) {
let obj = self.obj();
let child = if let Some(child) = obj.child().and_downcast::<gtk::Label>() {
child
} else {
let child = new_message_label();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_else::<gtk::Label>(new_message_label);
if EMOJI_REGEX.is_match(&text) {
child.add_css_class("emoji-message");
@ -279,13 +273,7 @@ mod imp {
}
let obj = self.obj();
let child = if let Some(child) = obj.child().and_downcast::<LabelWithWidgets>() {
child
} else {
let child = LabelWithWidgets::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<LabelWithWidgets>();
child.set_ellipsize(ellipsize);
child.set_use_markup(true);
@ -449,6 +437,8 @@ impl Default for MessageText {
}
}
impl IsABin for MessageText {}
/// Whether the given [`FormattedBody`] contains HTML.
fn formatted_body_is_html(formatted: &FormattedBody) -> bool {
formatted.format == MessageFormat::Html && !formatted.body.contains("<!-- raw HTML omitted -->")

33
src/session/view/content/room_history/mod.rs

@ -530,24 +530,12 @@ mod imp {
};
if let Some(event) = item.downcast_ref::<Event>() {
let child = if let Some(child) = list_item.child().and_downcast::<EventRow>() {
child
} else {
let child = EventRow::new(&self.obj());
list_item.set_child(Some(&child));
child
};
let child = list_item.child_or_else::<EventRow>(|| EventRow::new(&self.obj()));
child.set_event(Some(event.clone()));
} else if let Some(virtual_item) = item.downcast_ref::<VirtualItem>() {
set_virtual_item_child(list_item, virtual_item);
} else if let Some(group) = item.downcast_ref::<GroupingListGroup>() {
let child = if let Some(child) = list_item.child().and_downcast::<StateGroupRow>() {
child
} else {
let child = StateGroupRow::new();
list_item.set_child(Some(&child));
child
};
let child = list_item.child_or_default::<StateGroupRow>();
child.set_group(Some(group.clone()));
} else {
error!("Could not build widget for unsupported room history item: {item:?}");
@ -1167,27 +1155,14 @@ fn set_virtual_item_child(list_item: &gtk::ListItem, virtual_item: &VirtualItem)
}
}
VirtualItemKind::Typing => {
let child = if let Some(child) = list_item.child().and_downcast::<TypingRow>() {
child
} else {
let child = TypingRow::new();
list_item.set_child(Some(&child));
child
};
let child = list_item.child_or_default::<TypingRow>();
let typing_list = virtual_item.room().typing_list();
child.set_list(Some(typing_list));
}
VirtualItemKind::TimelineStart
| VirtualItemKind::DayDivider(_)
| VirtualItemKind::NewMessages => {
let divider = if let Some(divider) = list_item.child().and_downcast::<DividerRow>() {
divider
} else {
let divider = DividerRow::new();
list_item.set_child(Some(&divider));
divider
};
let divider = list_item.child_or_default::<DividerRow>();
divider.set_virtual_item(Some(virtual_item));
}
}

43
src/session/view/content/room_history/state/content.rs

@ -1,6 +1,6 @@
use adw::{prelude::*, subclass::prelude::*};
use gettextrs::gettext;
use gtk::glib;
use gtk::{glib, pango};
use matrix_sdk_ui::timeline::{
AnyOtherFullStateEventContent, MemberProfileChange, MembershipChange, OtherState,
RoomMembershipChange, TimelineItemContent,
@ -105,11 +105,8 @@ mod imp {
let obj = self.obj();
match widget {
WidgetType::Text(message) => {
if let Some(child) = obj.child().and_downcast::<gtk::Label>() {
child.set_text(&message);
} else {
obj.set_child(Some(&text(&message)));
}
let child = obj.child_or_else::<gtk::Label>(text);
child.set_label(&message);
}
WidgetType::Creation(widget) => obj.set_child(Some(&widget)),
WidgetType::Tombstone(widget) => obj.set_child(Some(&widget)),
@ -223,12 +220,8 @@ mod imp {
}
};
let obj = self.obj();
if let Some(child) = obj.child().and_downcast::<gtk::Label>() {
child.set_text(&message);
} else {
obj.set_child(Some(&text(&message)));
}
let child = self.obj().child_or_else::<gtk::Label>(text);
child.set_label(&message);
}
/// Convert a received membership change to a supported membership
@ -349,12 +342,8 @@ mod imp {
gettext_f("{user} joined this room.", &[("user", display_name)])
};
let obj = self.obj();
if let Some(child) = obj.child().and_downcast::<gtk::Label>() {
child.set_text(&message);
} else {
obj.set_child(Some(&text(&message)));
}
let child = self.obj().child_or_else::<gtk::Label>(text);
child.set_label(&message);
}
}
}
@ -377,18 +366,20 @@ impl Default for StateContent {
}
}
impl IsABin for StateContent {}
enum WidgetType {
Text(String),
Creation(StateCreation),
Tombstone(StateTombstone),
}
/// Construct a `GtkLabel` for the given text.
fn text(label: &str) -> gtk::Label {
let child = gtk::Label::new(Some(label));
child.set_css_classes(&["dimmed"]);
child.set_wrap(true);
child.set_wrap_mode(gtk::pango::WrapMode::WordChar);
child.set_xalign(0.0);
child
/// Construct a `GtkLabel` for presenting a state content.
fn text() -> gtk::Label {
gtk::Label::builder()
.css_classes(["dimmed"])
.wrap(true)
.wrap_mode(pango::WrapMode::WordChar)
.xalign(0.0)
.build()
}

6
src/session/view/content/room_history/state/group_row.rs

@ -246,3 +246,9 @@ impl StateGroupRow {
glib::Object::new()
}
}
impl Default for StateGroupRow {
fn default() -> Self {
Self::new()
}
}

6
src/session/view/content/room_history/state/row.rs

@ -56,3 +56,9 @@ impl StateRow {
glib::Object::new()
}
}
impl Default for StateRow {
fn default() -> Self {
Self::new()
}
}

6
src/session/view/content/room_history/typing_row.rs

@ -162,3 +162,9 @@ impl TypingRow {
glib::Object::new()
}
}
impl Default for TypingRow {
fn default() -> Self {
Self::new()
}
}

6
src/session/view/sidebar/icon_item_row.rs

@ -76,3 +76,9 @@ impl SidebarIconItemRow {
glib::Object::new()
}
}
impl Default for SidebarIconItemRow {
fn default() -> Self {
Self::new()
}
}

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

@ -298,3 +298,9 @@ impl SidebarRoomRow {
glib::Object::new()
}
}
impl Default for SidebarRoomRow {
fn default() -> Self {
Self::new()
}
}

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

@ -172,24 +172,14 @@ mod imp {
if let Some(item) = item {
if let Some(section) = item.downcast_ref::<SidebarSection>() {
let child = if let Some(child) = obj.child().and_downcast::<SidebarSectionRow>()
{
child
} else {
let child = obj.child_or_else::<SidebarSectionRow>(|| {
let child = SidebarSectionRow::new();
obj.set_child(Some(&child));
obj.update_relation(&[Relation::LabelledBy(&[child.labelled_by()])]);
child
};
});
child.set_section(Some(section.clone()));
} else if let Some(room) = item.downcast_ref::<Room>() {
let child = if let Some(child) = obj.child().and_downcast::<SidebarRoomRow>() {
child
} else {
let child = SidebarRoomRow::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<SidebarRoomRow>();
let room_is_direct_handler = room.connect_is_direct_notify(clone!(
#[weak(rename_to = imp)]
@ -222,26 +212,10 @@ mod imp {
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::<SidebarIconItemRow>() {
child
} else {
let child = SidebarIconItemRow::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<SidebarIconItemRow>();
child.set_icon_item(Some(icon_item.clone()));
} else if let Some(verification) = item.downcast_ref::<IdentityVerification>() {
let child =
if let Some(child) = obj.child().and_downcast::<SidebarVerificationRow>() {
child
} else {
let child = SidebarVerificationRow::new();
obj.set_child(Some(&child));
child
};
let child = obj.child_or_default::<SidebarVerificationRow>();
child.set_identity_verification(Some(verification.clone()));
} else {
panic!("Wrong row item: {item:?}");
@ -810,3 +784,13 @@ impl SidebarRow {
glib::Object::builder().property("sidebar", sidebar).build()
}
}
impl ChildPropertyExt for SidebarRow {
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child);
}
}

6
src/session/view/sidebar/verification_row.rs

@ -64,3 +64,9 @@ impl SidebarVerificationRow {
glib::Object::new()
}
}
impl Default for SidebarVerificationRow {
fn default() -> Self {
Self::new()
}
}

74
src/utils/mod.rs

@ -10,11 +10,12 @@ use std::{
sync::{Arc, LazyLock},
};
use adw::prelude::*;
use futures_util::{
future::{self, Either, Future},
pin_mut,
};
use gtk::{gio, glib, prelude::*};
use gtk::{gio, glib};
use regex::Regex;
use tempfile::NamedTempFile;
@ -634,3 +635,74 @@ impl Drop for CountedRef {
}
}
}
/// Extensions trait for types with a `child` property.
pub(crate) trait ChildPropertyExt {
/// The child of this widget, is any.
fn child_property(&self) -> Option<gtk::Widget>;
/// Set the child of this widget.
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>);
/// Get the child if it is of the proper type, or construct it with the
/// given function and set is as the child of this widget before returning
/// it.
fn child_or_else<W>(&self, f: impl FnOnce() -> W) -> W
where
W: IsA<gtk::Widget>,
{
if let Some(child) = self.child_property().and_downcast() {
child
} else {
let child = f();
self.set_child_property(Some(&child));
child
}
}
/// Get the child if it is of the proper type, or construct it with its
/// `Default` implementation and set is as the child of this widget before
/// returning it.
fn child_or_default<W>(&self) -> W
where
W: IsA<gtk::Widget> + Default,
{
self.child_or_else(Default::default)
}
}
impl<W> ChildPropertyExt for W
where
W: IsABin,
{
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child);
}
}
impl ChildPropertyExt for gtk::ListItem {
fn child_property(&self) -> Option<gtk::Widget> {
self.child()
}
fn set_child_property(&self, child: Option<&impl IsA<gtk::Widget>>) {
self.set_child(child);
}
}
/// Helper trait to implement for widgets that subclass `AdwBin`, to be able to
/// use the `ChildPropertyExt` trait.
///
/// This trait is to circumvent conflicts in Rust's type system, where if we try
/// to implement `ChildPropertyExt for W where W: IsA<adw::Bin>` it complains
/// that the other external types that implement `ChildPropertyExt` might
/// implement `IsA<adw::Bin>` in the future… So instead of reimplementing
/// `ChildPropertyExt` for every type where we need it, which requires to
/// implement two methods, we only implement this which requires nothing.
pub(crate) trait IsABin: IsA<adw::Bin> {}
impl IsABin for adw::Bin {}

Loading…
Cancel
Save