[fractal/multi-account: 10/10] Account switcher: Show hints in account entry for visible session
- From: Alejandro Domínguez <aledomu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal/multi-account: 10/10] Account switcher: Show hints in account entry for visible session
- Date: Fri, 27 Aug 2021 01:14:15 +0000 (UTC)
commit 3490ee1eecd623d22d237de7adcf70a503fd652c
Author: Alejandro Domínguez <adomu net-c com>
Date: Mon Aug 16 05:41:05 2021 +0200
Account switcher: Show hints in account entry for visible session
data/resources/resources.gresource.xml | 1 +
data/resources/style.css | 10 ++
.../ui/components-avatar-with-selection.ui | 12 ++
data/resources/ui/user-entry-row.ui | 7 +-
po/POTFILES.in | 2 +
src/components/avatar_with_selection.rs | 141 +++++++++++++++++++++
src/components/mod.rs | 2 +
src/meson.build | 1 +
src/session/sidebar/account_switcher/user_entry.rs | 44 +++++--
9 files changed, 208 insertions(+), 12 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index ca7c77b3..0139dbb9 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -33,6 +33,7 @@
<file compressed="true" preprocess="xml-stripblanks"
alias="spinner-button.ui">ui/spinner-button.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="in-app-notification.ui">ui/in-app-notification.ui</file>
<file compressed="true" preprocess="xml-stripblanks"
alias="components-avatar.ui">ui/components-avatar.ui</file>
+ <file compressed="true" preprocess="xml-stripblanks"
alias="components-avatar-with-selection.ui">ui/components-avatar-with-selection.ui</file>
<file compressed="true">style.css</file>
<file preprocess="xml-stripblanks">icons/scalable/actions/send-symbolic.svg</file>
<file preprocess="xml-stripblanks">icons/scalable/status/welcome.svg</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index 08e2583f..f9486b67 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -55,6 +55,16 @@
background-color: alpha(@theme_bg_color, 0.2);
}
+.selected-avatar avatar {
+ border: 2px solid @accent_bg_color;
+}
+
+.blue-checkmark {
+ color: @accent_bg_color;
+ border-radius: 9999px;
+ background-color: white;
+}
+
/* Login */
.login {
min-width: 250px;
diff --git a/data/resources/ui/components-avatar-with-selection.ui
b/data/resources/ui/components-avatar-with-selection.ui
new file mode 100644
index 00000000..ed1d4c75
--- /dev/null
+++ b/data/resources/ui/components-avatar-with-selection.ui
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="ComponentsAvatarWithSelection" parent="AdwBin">
+ <property name="child">
+ <object class="GtkOverlay" id="checkmark_overlay">
+ <child>
+ <object class="ComponentsAvatar" id="child_avatar"></object>
+ </child>
+ </object>
+ </property>
+ </template>
+</interface>
diff --git a/data/resources/ui/user-entry-row.ui b/data/resources/ui/user-entry-row.ui
index 611fc607..beaed5c5 100644
--- a/data/resources/ui/user-entry-row.ui
+++ b/data/resources/ui/user-entry-row.ui
@@ -1,11 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="UserEntryRow" parent="AdwBin">
+ <binding name="active">
+ <lookup name="visible" type="GtkStackPage">
+ <lookup name="session-page">UserEntryRow</lookup>
+ </lookup>
+ </binding>
<child>
<object class="GtkBox">
<property name="spacing">10</property>
<child>
- <object class="ComponentsAvatar" id="avatar_component">
+ <object class="ComponentsAvatarWithSelection" id="avatar_component">
<property name="size">40</property>
<binding name="item">
<lookup name="avatar" type="User">
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 554422f7..8070be19 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,7 @@ data/org.gnome.FractalNext.metainfo.xml.in.in
# UI files
data/resources/ui/add_account.ui
data/resources/ui/components-avatar.ui
+data/resources/ui/components-avatar-with-selection.ui
data/resources/ui/content-divider-row.ui
data/resources/ui/content-item-row-menu.ui
data/resources/ui/content-item.ui
@@ -36,6 +37,7 @@ data/resources/ui/window.ui
# Rust files
src/application.rs
src/components/avatar.rs
+src/components/avatar_with_selection.rs
src/components/context_menu_bin.rs
src/components/custom_entry.rs
src/components/label_with_widgets.rs
diff --git a/src/components/avatar_with_selection.rs b/src/components/avatar_with_selection.rs
new file mode 100644
index 00000000..ecf47dd0
--- /dev/null
+++ b/src/components/avatar_with_selection.rs
@@ -0,0 +1,141 @@
+use adw::subclass::prelude::*;
+use gtk::{glib, prelude::*, subclass::prelude::*, CompositeTemplate};
+
+use super::Avatar;
+use crate::session::Avatar as AvatarItem;
+
+mod imp {
+ use super::*;
+ use glib::subclass::InitializingObject;
+ use once_cell::sync::{Lazy, OnceCell};
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/FractalNext/components-avatar-with-selection.ui")]
+ pub struct AvatarWithSelection {
+ #[template_child]
+ pub child_avatar: TemplateChild<Avatar>,
+ #[template_child]
+ pub checkmark_overlay: TemplateChild<gtk::Overlay>,
+ pub checkmark: OnceCell<gtk::Image>,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for AvatarWithSelection {
+ const NAME: &'static str = "ComponentsAvatarWithSelection";
+ type Type = super::AvatarWithSelection;
+ type ParentType = adw::Bin;
+
+ fn class_init(klass: &mut Self::Class) {
+ Avatar::static_type();
+ Self::bind_template(klass);
+ }
+
+ fn instance_init(obj: &InitializingObject<Self>) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for AvatarWithSelection {
+ fn properties() -> &'static [glib::ParamSpec] {
+ static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+ vec![
+ glib::ParamSpec::new_object(
+ "item",
+ "Item",
+ "The Avatar item displayed by this widget",
+ AvatarItem::static_type(),
+ glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+ ),
+ glib::ParamSpec::new_int(
+ "size",
+ "Size",
+ "The size of the Avatar",
+ -1,
+ i32::MAX,
+ -1,
+ glib::ParamFlags::READWRITE,
+ ),
+ glib::ParamSpec::new_boolean(
+ "selected",
+ "Selected",
+ "Style helper for the inner Avatar",
+ false,
+ glib::ParamFlags::WRITABLE,
+ ),
+ ]
+ });
+
+ PROPERTIES.as_ref()
+ }
+
+ fn set_property(
+ &self,
+ obj: &Self::Type,
+ _id: usize,
+ value: &glib::Value,
+ pspec: &glib::ParamSpec,
+ ) {
+ match pspec.name() {
+ "item" => self.child_avatar.set_item(value.get().unwrap()),
+ "size" => self.child_avatar.set_size(value.get().unwrap()),
+ "selected" => obj.set_selected(value.get().unwrap()),
+ _ => unimplemented!(),
+ }
+ }
+
+ fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+ match pspec.name() {
+ "item" => self.child_avatar.item().to_value(),
+ "size" => self.child_avatar.size().to_value(),
+ _ => unimplemented!(),
+ }
+ }
+ }
+
+ impl WidgetImpl for AvatarWithSelection {}
+ impl BinImpl for AvatarWithSelection {}
+}
+
+glib::wrapper! {
+ /// A widget displaying an `Avatar` for a `Room` or `User`.
+ pub struct AvatarWithSelection(ObjectSubclass<imp::AvatarWithSelection>)
+ @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl AvatarWithSelection {
+ pub fn new() -> Self {
+ glib::Object::new(&[]).expect("Failed to create AvatarWithSelection")
+ }
+
+ pub fn set_selected(&self, selected: bool) {
+ let priv_ = imp::AvatarWithSelection::from_instance(self);
+ let checkmark = priv_.checkmark.get_or_init(|| {
+ gtk::Image::builder()
+ .halign(gtk::Align::End)
+ .valign(gtk::Align::End)
+ .icon_name("emblem-default-symbolic")
+ .pixel_size(14)
+ .css_classes(vec!["blue-checkmark".into()])
+ .build()
+ });
+
+ if selected {
+ priv_.child_avatar.add_css_class("selected-avatar");
+ priv_.checkmark_overlay.add_overlay(checkmark);
+ } else {
+ priv_.child_avatar.remove_css_class("selected-avatar");
+ priv_.checkmark_overlay.remove_overlay(checkmark);
+ }
+ }
+
+ pub fn avatar(&self) -> &Avatar {
+ let priv_ = imp::AvatarWithSelection::from_instance(self);
+ &priv_.child_avatar
+ }
+}
+
+impl Default for AvatarWithSelection {
+ fn default() -> Self {
+ Self::new()
+ }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index d80ef5a3..3e1a2915 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -1,4 +1,5 @@
mod avatar;
+mod avatar_with_selection;
mod context_menu_bin;
mod custom_entry;
mod in_app_notification;
@@ -8,6 +9,7 @@ mod room_title;
mod spinner_button;
pub use self::avatar::Avatar;
+pub use self::avatar_with_selection::AvatarWithSelection;
pub use self::context_menu_bin::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl};
pub use self::custom_entry::CustomEntry;
pub use self::in_app_notification::InAppNotification;
diff --git a/src/meson.build b/src/meson.build
index 193238ca..f9eb7fc2 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -21,6 +21,7 @@ run_command(
sources = files(
'application.rs',
'components/avatar.rs',
+ 'components/avatar_with_selection.rs',
'components/context_menu_bin.rs',
'components/custom_entry.rs',
'components/label_with_widgets.rs',
diff --git a/src/session/sidebar/account_switcher/user_entry.rs
b/src/session/sidebar/account_switcher/user_entry.rs
index 6edeaf14..a752562b 100644
--- a/src/session/sidebar/account_switcher/user_entry.rs
+++ b/src/session/sidebar/account_switcher/user_entry.rs
@@ -1,4 +1,4 @@
-use crate::components::Avatar;
+use crate::components::AvatarWithSelection;
use adw::subclass::prelude::BinImpl;
use gtk::{self, glib, prelude::*, subclass::prelude::*, CompositeTemplate};
@@ -12,7 +12,7 @@ mod imp {
#[template(resource = "/org/gnome/FractalNext/user-entry-row.ui")]
pub struct UserEntryRow {
#[template_child]
- pub avatar_component: TemplateChild<Avatar>,
+ pub avatar_component: TemplateChild<AvatarWithSelection>,
#[template_child]
pub display_name: TemplateChild<gtk::Label>,
#[template_child]
@@ -27,7 +27,7 @@ mod imp {
type ParentType = adw::Bin;
fn class_init(klass: &mut Self::Class) {
- Avatar::static_type();
+ AvatarWithSelection::static_type();
Self::bind_template(klass);
}
@@ -39,13 +39,22 @@ mod imp {
impl ObjectImpl for UserEntryRow {
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
- vec![glib::ParamSpec::new_object(
- "session-page",
- "Session StackPage",
- "The stack page of the session that this entry represents",
- gtk::StackPage::static_type(),
- glib::ParamFlags::READWRITE,
- )]
+ vec![
+ glib::ParamSpec::new_boolean(
+ "active",
+ "Active",
+ "Is the active user",
+ false,
+ glib::ParamFlags::WRITABLE,
+ ),
+ glib::ParamSpec::new_object(
+ "session-page",
+ "Session StackPage",
+ "The stack page of the session that this entry represents",
+ gtk::StackPage::static_type(),
+ glib::ParamFlags::READWRITE,
+ ),
+ ]
});
PROPERTIES.as_ref()
@@ -53,12 +62,16 @@ mod imp {
fn set_property(
&self,
- _obj: &Self::Type,
+ obj: &Self::Type,
_id: usize,
value: &glib::Value,
pspec: &glib::ParamSpec,
) {
match pspec.name() {
+ "active" => {
+ let active = value.get().unwrap();
+ obj.set_active(active);
+ }
"session-page" => {
let session_page = value.get().unwrap();
self.session_page.replace(Some(session_page));
@@ -88,4 +101,13 @@ impl UserEntryRow {
pub fn new(session_page: >k::StackPage) -> Self {
glib::Object::new(&[("session-page", session_page)]).expect("Failed to create UserEntryRow")
}
+
+ pub fn set_active(&self, active: bool) {
+ let priv_ = imp::UserEntryRow::from_instance(self);
+
+ priv_.avatar_component.set_selected(active);
+ priv_
+ .display_name
+ .set_css_classes(if active { &["bold"] } else { &[] });
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]