use adw::{prelude::*, subclass::prelude::*};
use gtk::{glib, glib::clone, CompositeTemplate};
mod at_room;
mod search_entry;
mod source;
mod source_row;
pub use self::{
at_room::AtRoom,
search_entry::PillSearchEntry,
source::{PillSource, PillSourceExt, PillSourceImpl},
source_row::PillSourceRow,
};
use super::{Avatar, JoinRoomDialog, UserProfileDialog};
use crate::{
prelude::*,
session::{
model::{Member, RemoteRoom, Room},
view::SessionView,
},
utils::{add_activate_binding_action, BoundObject},
};
mod imp {
use std::cell::{Cell, RefCell};
use glib::subclass::InitializingObject;
use super::*;
#[derive(Debug, Default, CompositeTemplate, glib::Properties)]
#[template(resource = "/org/gnome/Fractal/ui/components/pill/mod.ui")]
#[properties(wrapper_type = super::Pill)]
pub struct Pill {
#[template_child]
pub content: TemplateChild<gtk::Box>,
#[template_child]
pub display_name: TemplateChild<gtk::Label>,
#[template_child]
pub avatar: TemplateChild<Avatar>,
#[property(get, set = Self::set_source, explicit_notify, nullable)]
pub source: BoundObject<PillSource>,
#[property(get, set = Self::set_activatable, explicit_notify)]
pub activatable: Cell<bool>,
gesture_click: RefCell<Option<gtk::GestureClick>>,
}
#[glib::object_subclass]
impl ObjectSubclass for Pill {
const NAME: &'static str = "Pill";
type Type = super::Pill;
type ParentType = gtk::Widget;
fn class_init(klass: &mut Self::Class) {
Self::bind_template(klass);
klass.set_layout_manager_type::<gtk::BinLayout>();
klass.set_css_name("inline-pill");
klass.install_action("pill.activate", None, |obj, _, _| {
obj.activate();
});
add_activate_binding_action(klass, "pill.activate");
}
fn instance_init(obj: &InitializingObject<Self>) {
obj.init_template();
}
}
#[glib::derived_properties]
impl ObjectImpl for Pill {
fn constructed(&self) {
self.parent_constructed();
self.update_activatable_state();
}
fn dispose(&self) {
self.content.unparent();
}
}
impl WidgetImpl for Pill {}
impl Pill {
fn set_source(&self, source: Option<PillSource>) {
if self.source.obj() == source {
return;
}
self.source.disconnect_signals();
if let Some(source) = source {
let display_name_handler = source.connect_disambiguated_name_notify(clone!(
#[weak(rename_to = imp)]
self,
move |source| {
imp.set_display_name(&source.disambiguated_name());
}
));
self.set_display_name(&source.disambiguated_name());
self.source.set(source, vec![display_name_handler]);
}
self.obj().notify_source();
}
fn set_activatable(&self, activatable: bool) {
if self.activatable.get() == activatable {
return;
}
let obj = self.obj();
if let Some(gesture_click) = self.gesture_click.take() {
obj.remove_controller(&gesture_click);
}
self.activatable.set(activatable);
if activatable {
let gesture_click = gtk::GestureClick::new();
gesture_click.connect_released(clone!(
#[weak]
obj,
move |_, _, _, _| {
obj.activate();
}
));
obj.add_controller(gesture_click.clone());
self.gesture_click.replace(Some(gesture_click));
}
self.update_activatable_state();
obj.notify_activatable();
}
fn update_activatable_state(&self) {
let obj = self.obj();
let activatable = self.activatable.get();
obj.action_set_enabled("pill.activate", activatable);
obj.set_focusable(activatable);
let role = if activatable {
gtk::AccessibleRole::Link
} else {
gtk::AccessibleRole::Group
};
obj.set_accessible_role(role);
if activatable {
obj.add_css_class("activatable");
} else {
obj.remove_css_class("activatable");
}
}
fn set_display_name(&self, label: &str) {
let mut maybe_ellipsized = label.chars().take(30).collect::<String>();
let is_ellipsized = maybe_ellipsized.len() < label.len();
if is_ellipsized {
maybe_ellipsized.append_ellipsis();
}
self.display_name.set_label(&maybe_ellipsized);
}
}
}
glib::wrapper! {
pub struct Pill(ObjectSubclass<imp::Pill>)
@extends gtk::Widget, @implements gtk::Accessible;
}
impl Pill {
pub fn new(source: &impl IsA<PillSource>) -> Self {
glib::Object::builder().property("source", source).build()
}
fn activate(&self) {
let Some(source) = self.source() else {
return;
};
if let Some(member) = source.downcast_ref::<Member>() {
let dialog = UserProfileDialog::new();
dialog.set_room_member(member.clone());
dialog.present(Some(self));
} else if let Some(room) = source.downcast_ref::<Room>() {
let Some(session_view) = self
.ancestor(SessionView::static_type())
.and_downcast::<SessionView>()
else {
return;
};
session_view.select_room(Some(room.clone()));
} else if let Ok(room) = source.downcast::<RemoteRoom>() {
let Some(session) = room.session() else {
return;
};
let dialog = JoinRoomDialog::new(&session);
dialog.set_room(room);
dialog.present(Some(self));
}
}
}