[fractal] power_level badges for users in room settings
- From: Daniel Garcia Moreno <danigm src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] power_level badges for users in room settings
- Date: Wed, 20 Mar 2019 09:11:44 +0000 (UTC)
commit d60ce58cf3ea98b60862df3d3e9384a86c4de80e
Author: Crom (Thibaut CHARLES) <CromFr gmail com>
Date: Fri Mar 8 22:52:07 2019 +0100
power_level badges for users in room settings
Used Avatar::circle badge parameter in MemberBox
rustfmt
Show power level next to the matrix user ID
Fix mxid & power level forcing window width
new design closer to @bertob's proposal
Gap around avatar's badge
Power level roles translation
css-themed avatar's colored dot
Reduced badge height
fractal-gtk/res/app.css | 19 ++++
fractal-gtk/src/appop/account.rs | 2 +-
fractal-gtk/src/appop/user.rs | 4 +-
fractal-gtk/src/widgets/avatar.rs | 150 +++++++++++++++----------------
fractal-gtk/src/widgets/member.rs | 28 +++---
fractal-gtk/src/widgets/members_list.rs | 92 ++++++++++++++++---
fractal-gtk/src/widgets/message.rs | 8 +-
fractal-gtk/src/widgets/mod.rs | 3 +-
fractal-gtk/src/widgets/room.rs | 2 +-
fractal-gtk/src/widgets/room_settings.rs | 11 ++-
fractal-gtk/src/widgets/roomrow.rs | 4 +-
11 files changed, 205 insertions(+), 118 deletions(-)
---
diff --git a/fractal-gtk/res/app.css b/fractal-gtk/res/app.css
index bd38d5f6..4680520d 100644
--- a/fractal-gtk/res/app.css
+++ b/fractal-gtk/res/app.css
@@ -299,3 +299,22 @@ stack.titlebar:not(headerbar) > box > separator {
margin-bottom: 8px;
color: @theme_selected_bg_color;
}
+
+
+.badge {
+ border-radius: 5px;
+ padding: 0.1em 5px;
+ font-size: 0.8em;
+}
+.badge-circle {
+ border-radius: 99999px;
+}
+.badge-gold {
+ background-color: #E5A50A;
+}
+.badge-silver {
+ background-color: #99A8B0;
+}
+.badge-grey {
+ background-color: #D9D9D9;
+}
\ No newline at end of file
diff --git a/fractal-gtk/src/appop/account.rs b/fractal-gtk/src/appop/account.rs
index 9116420c..c589bb02 100644
--- a/fractal-gtk/src/appop/account.rs
+++ b/fractal-gtk/src/appop/account.rs
@@ -456,7 +456,7 @@ impl AppOp {
avatar.add(&w);
let uid = self.uid.clone().unwrap_or_default();
- let data = w.circle(uid.clone(), self.username.clone(), 100);
+ let data = w.circle(uid.clone(), self.username.clone(), 100, None, None);
download_to_cache(self.backend.clone(), uid.clone(), data.clone());
/* FIXME: hack to make the avatar drawing area clickable*/
diff --git a/fractal-gtk/src/appop/user.rs b/fractal-gtk/src/appop/user.rs
index 88306a2d..8a000fc8 100644
--- a/fractal-gtk/src/appop/user.rs
+++ b/fractal-gtk/src/appop/user.rs
@@ -52,7 +52,7 @@ impl AppOp {
let w = widgets::Avatar::avatar_new(Some(40));
let uid = self.uid.clone().unwrap_or_default();
- let data = w.circle(uid.clone(), self.username.clone(), 40);
+ let data = w.circle(uid.clone(), self.username.clone(), 40, None, None);
download_to_cache(self.backend.clone(), uid.clone(), data.clone());
avatar.add(&w);
@@ -66,7 +66,7 @@ impl AppOp {
Some(_) => {
let w = widgets::Avatar::avatar_new(Some(24));
let uid = self.uid.clone().unwrap_or_default();
- let data = w.circle(uid.clone(), self.username.clone(), 24);
+ let data = w.circle(uid.clone(), self.username.clone(), 24, None, None);
download_to_cache(self.backend.clone(), uid.clone(), data.clone());
eb.add(&w);
diff --git a/fractal-gtk/src/widgets/avatar.rs b/fractal-gtk/src/widgets/avatar.rs
index 39350102..c2efc757 100644
--- a/fractal-gtk/src/widgets/avatar.rs
+++ b/fractal-gtk/src/widgets/avatar.rs
@@ -11,7 +11,13 @@ use gtk;
use gtk::prelude::*;
pub use gtk::DrawingArea;
-pub type Avatar = gtk::Box;
+pub enum AvatarBadgeColor {
+ Gold,
+ Silver,
+ Grey,
+}
+
+pub type Avatar = gtk::Overlay;
pub struct AvatarData {
uid: String,
@@ -39,13 +45,20 @@ impl AvatarData {
}
pub trait AvatarExt {
- fn avatar_new(size: Option<i32>) -> gtk::Box;
+ fn avatar_new(size: Option<i32>) -> gtk::Overlay;
fn clean(&self);
fn create_da(&self, size: Option<i32>) -> DrawingArea;
- fn circle(&self, uid: String, username: Option<String>, size: i32) -> Rc<RefCell<AvatarData>>;
+ fn circle(
+ &self,
+ uid: String,
+ username: Option<String>,
+ size: i32,
+ badge: Option<AvatarBadgeColor>,
+ badge_size: Option<i32>,
+ ) -> Rc<RefCell<AvatarData>>;
}
-impl AvatarExt for gtk::Box {
+impl AvatarExt for gtk::Overlay {
fn clean(&self) {
for ch in self.get_children().iter() {
self.remove(ch);
@@ -57,14 +70,14 @@ impl AvatarExt for gtk::Box {
let s = size.unwrap_or(40);
da.set_size_request(s, s);
- self.pack_start(&da, true, true, 0);
+ self.add(&da);
self.show_all();
da
}
- fn avatar_new(size: Option<i32>) -> gtk::Box {
- let b = gtk::Box::new(gtk::Orientation::Horizontal, 0);
+ fn avatar_new(size: Option<i32>) -> gtk::Overlay {
+ let b = gtk::Overlay::new();
b.create_da(size);
b.show_all();
if let Some(style) = b.get_style_context() {
@@ -73,8 +86,20 @@ impl AvatarExt for gtk::Box {
b
}
-
- fn circle(&self, uid: String, username: Option<String>, size: i32) -> Rc<RefCell<AvatarData>> {
+ /// # Arguments
+ /// * `uid` - Matrix ID
+ /// * `username` - Full name
+ /// * `size` - Size of the avatar
+ /// * `badge_color` - Badge color. None for no badge
+ /// * `badge_size` - Badge size. None for size / 3
+ fn circle(
+ &self,
+ uid: String,
+ username: Option<String>,
+ size: i32,
+ badge_color: Option<AvatarBadgeColor>,
+ badge_size: Option<i32>,
+ ) -> Rc<RefCell<AvatarData>> {
self.clean();
let da = self.create_da(Some(size));
let path = cache_path(&uid).unwrap_or_default();
@@ -90,6 +115,25 @@ impl AvatarExt for gtk::Box {
let fallback = letter_avatar::generate::new(uid.clone(), username, size as f64)
.expect("this function should never fail");
+ // Power level badge setup
+ let has_badge = badge_color.is_some();
+ let badge_size = badge_size.unwrap_or(size / 3);
+ if let Some(color) = badge_color {
+ let badge = gtk::Box::new(gtk::Orientation::Vertical, 0);
+ badge.set_size_request(badge_size, badge_size);
+ badge.set_valign(gtk::Align::Start);
+ badge.set_halign(gtk::Align::End);
+ if let Some(style) = badge.get_style_context() {
+ style.add_class("badge-circle");
+ style.add_class(match color {
+ AvatarBadgeColor::Gold => "badge-gold",
+ AvatarBadgeColor::Silver => "badge-silver",
+ AvatarBadgeColor::Grey => "badge-grey",
+ });
+ }
+ self.add_overlay(&badge);
+ }
+
let data = AvatarData {
uid: uid.clone(),
username: uname,
@@ -109,19 +153,32 @@ impl AvatarExt for gtk::Box {
g.set_antialias(cairo::Antialias::Best);
{
- let data = user_cache.borrow();
- if let Some(ref pb) = data.cache {
- let context = da.get_style_context().unwrap();
- gtk::render_background(&context, g, 0.0, 0.0, width, height);
-
+ g.set_fill_rule(cairo::FillRule::EvenOdd);
+ g.arc(
+ width / 2.0,
+ height / 2.0,
+ width.min(height) / 2.0,
+ 0.0,
+ 2.0 * PI,
+ );
+ if has_badge {
+ g.clip_preserve();
+ g.new_sub_path();
+ let badge_radius = badge_size as f64 / 2.0;
g.arc(
- width / 2.0,
- height / 2.0,
- width.min(height) / 2.0,
+ width - badge_radius,
+ badge_radius,
+ badge_radius * 1.4,
0.0,
2.0 * PI,
);
- g.clip();
+ }
+ g.clip();
+
+ let data = user_cache.borrow();
+ if let Some(ref pb) = data.cache {
+ let context = da.get_style_context().unwrap();
+ gtk::render_background(&context, g, 0.0, 0.0, width, height);
let hpos: f64 = (width - (pb.get_height()) as f64) / 2.0;
g.set_source_pixbuf(&pb, 0.0, hpos);
@@ -155,60 +212,3 @@ fn load_pixbuf(path: &str, size: i32) -> Option<Pixbuf> {
None
}
}
-
-pub enum AdminColor {
- Gold,
- Silver,
-}
-
-pub fn admin_badge(kind: AdminColor, size: Option<i32>) -> gtk::DrawingArea {
- let s = size.unwrap_or(10);
-
- let da = DrawingArea::new();
- da.set_size_request(s, s);
-
- let color = match kind {
- AdminColor::Gold => (237.0, 212.0, 0.0),
- AdminColor::Silver => (186.0, 186.0, 186.0),
- };
-
- let border = match kind {
- AdminColor::Gold => (107.0, 114.0, 0.0),
- AdminColor::Silver => (137.0, 137.0, 137.0),
- };
-
- da.connect_draw(move |da, g| {
- use std::f64::consts::PI;
- g.set_antialias(cairo::Antialias::Best);
-
- let width = s as f64;
- let height = s as f64;
-
- let context = da.get_style_context().unwrap();
- gtk::render_background(&context, g, 0.0, 0.0, width, height);
-
- g.set_source_rgba(color.0 / 256.0, color.1 / 256.0, color.2 / 256.0, 1.);
- g.arc(
- width / 2.0,
- height / 2.0,
- width.min(height) / 2.5,
- 0.0,
- 2.0 * PI,
- );
- g.fill();
-
- g.set_source_rgba(border.0 / 256.0, border.1 / 256.0, border.2 / 256.0, 0.5);
- g.arc(
- width / 2.0,
- height / 2.0,
- width.min(height) / 2.5,
- 0.0,
- 2.0 * PI,
- );
- g.stroke();
-
- Inhibit(false)
- });
-
- da
-}
diff --git a/fractal-gtk/src/widgets/member.rs b/fractal-gtk/src/widgets/member.rs
index c3447dc5..e4760fc8 100644
--- a/fractal-gtk/src/widgets/member.rs
+++ b/fractal-gtk/src/widgets/member.rs
@@ -54,10 +54,17 @@ impl<'a> MemberBox<'a> {
}
let avatar = widgets::Avatar::avatar_new(Some(globals::USERLIST_ICON_SIZE));
+ let badge = match self.op.member_level(self.member) {
+ 100 => Some(widgets::AvatarBadgeColor::Gold),
+ 50...100 => Some(widgets::AvatarBadgeColor::Silver),
+ _ => None,
+ };
let data = avatar.circle(
self.member.uid.clone(),
Some(alias.clone()),
globals::USERLIST_ICON_SIZE,
+ badge,
+ None,
);
let member_id = self.member.uid.clone();
download_to_cache(backend.clone(), member_id.clone(), data.clone());
@@ -71,24 +78,7 @@ impl<'a> MemberBox<'a> {
v.pack_start(&uid, true, true, 0);
}
- match self.op.member_level(self.member) {
- 100 => {
- let overlay = gtk::Overlay::new();
- overlay.add(&avatar);
- overlay.add_overlay(&widgets::admin_badge(widgets::AdminColor::Gold, None));
- w.add(&overlay);
- }
- 50 => {
- let overlay = gtk::Overlay::new();
- overlay.add(&avatar);
- overlay.add_overlay(&widgets::admin_badge(widgets::AdminColor::Silver, None));
- w.add(&overlay);
- }
- _ => {
- w.add(&avatar);
- }
- }
-
+ w.add(&avatar);
w.add(&v);
event_box.add(&w);
@@ -113,6 +103,8 @@ impl<'a> MemberBox<'a> {
self.member.uid.clone(),
Some(self.member.get_alias()),
globals::PILL_ICON_SIZE,
+ None,
+ None,
);
let member_id = self.member.uid.clone();
download_to_cache(backend.clone(), member_id.clone(), data.clone());
diff --git a/fractal-gtk/src/widgets/members_list.rs b/fractal-gtk/src/widgets/members_list.rs
index b55f3009..43a1a40c 100644
--- a/fractal-gtk/src/widgets/members_list.rs
+++ b/fractal-gtk/src/widgets/members_list.rs
@@ -1,5 +1,6 @@
use fractal_api::clone;
use std::cell::RefCell;
+use std::collections::hash_map::HashMap;
use std::rc::Rc;
use glib::signal;
@@ -9,7 +10,7 @@ use gtk::prelude::*;
use crate::i18n::i18n;
use crate::types::Member;
use crate::widgets;
-use crate::widgets::avatar::AvatarExt;
+use crate::widgets::avatar::{AvatarBadgeColor, AvatarExt};
#[derive(Debug, Clone)]
pub struct MembersList {
@@ -17,15 +18,21 @@ pub struct MembersList {
search_entry: gtk::SearchEntry,
error: gtk::Label,
members: Vec<Member>,
+ power_levels: HashMap<String, i32>,
}
impl MembersList {
- pub fn new(m: Vec<Member>, entry: gtk::SearchEntry) -> MembersList {
+ pub fn new(
+ m: Vec<Member>,
+ power_levels: HashMap<String, i32>,
+ entry: gtk::SearchEntry,
+ ) -> MembersList {
MembersList {
container: gtk::ListBox::new(),
error: gtk::Label::new(None),
members: m,
search_entry: entry,
+ power_levels: power_levels,
}
}
@@ -35,7 +42,11 @@ impl MembersList {
let b = gtk::Box::new(gtk::Orientation::Vertical, 0);
b.set_hexpand(true);
b.pack_start(&self.container, true, true, 0);
- add_rows(self.container.clone(), self.members.clone());
+ add_rows(
+ self.container.clone(),
+ self.members.clone(),
+ self.power_levels.clone(),
+ );
self.error
.get_style_context()?
.add_class("no_member_search");
@@ -105,11 +116,11 @@ impl MembersList {
}
}
-fn create_row(member: Member) -> Option<gtk::ListBoxRow> {
+fn create_row(member: Member, power_level: Option<i32>) -> Option<gtk::ListBoxRow> {
let row = gtk::ListBoxRow::new();
row.connect_draw(clone!(member => move |w, _| {
if w.get_child().is_none() {
- w.add(&load_row_content(member.clone()));
+ w.add(&load_row_content(member.clone(), power_level));
}
gtk::Inhibit(false)
}));
@@ -120,40 +131,93 @@ fn create_row(member: Member) -> Option<gtk::ListBoxRow> {
}
/* creating the row is quite slow, therefore we have a small delay when scrolling the members list */
-fn load_row_content(member: Member) -> gtk::Box {
+fn load_row_content(member: Member, power_level: Option<i32>) -> gtk::Box {
let b = gtk::Box::new(gtk::Orientation::Horizontal, 12);
+
+ // Power level badge colour
+ let pl = power_level.unwrap_or_default();
+ let badge_color = match pl {
+ 100 => Some(AvatarBadgeColor::Gold),
+ 50...99 => Some(AvatarBadgeColor::Silver),
+ 1...49 => Some(AvatarBadgeColor::Grey),
+ _ => None,
+ };
+
+ // Avatar
let avatar = widgets::Avatar::avatar_new(Some(40));
- avatar.circle(member.uid.clone(), member.alias.clone(), 40);
- let user_box = gtk::Box::new(gtk::Orientation::Vertical, 0);
+ avatar.circle(
+ member.uid.clone(),
+ member.alias.clone(),
+ 40,
+ badge_color,
+ None,
+ );
+
+ let user_box = gtk::Box::new(gtk::Orientation::Vertical, 0); // Name & badge + Matrix ID
+ let username_box = gtk::Box::new(gtk::Orientation::Horizontal, 0); // Name + badge
+
let username = gtk::Label::new(Some(member.get_alias().as_str()));
- let uid = gtk::Label::new(Some(member.uid.as_str()));
username.set_xalign(0.);
username.set_margin_end(5);
username.set_ellipsize(pango::EllipsizeMode::End);
+ username_box.pack_start(&username, false, false, 0);
+
+ // Power level badge colour
+ let pl = power_level.unwrap_or_default();
+ if pl > 0 && pl <= 100 {
+ let badge_data = match pl {
+ 100 => (i18n("Admin"), "badge-gold"),
+ 50...99 => (i18n("Moderator"), "badge-silver"),
+ 1...49 => (i18n("Privileged"), "badge-grey"),
+ _ => panic!(),
+ };
+
+ let badge_wid = gtk::Label::new(Some(format!("{} ({})", badge_data.0, pl).as_str()));
+ badge_wid.set_valign(gtk::Align::Center);
+ if let Some(style) = badge_wid.get_style_context() {
+ style.add_class("badge");
+ style.add_class(badge_data.1);
+ }
+ username_box.pack_start(&badge_wid, false, false, 0);
+ }
+
+ // matrix ID + power level
+ let uid = gtk::Label::new(Some(member.uid.as_str()));
uid.set_xalign(0.);
+ uid.set_line_wrap(true);
+ uid.set_line_wrap_mode(pango::WrapMode::Char);
if let Some(style) = uid.get_style_context() {
style.add_class("small-font");
style.add_class("dim-label");
}
+
b.set_margin_start(12);
b.set_margin_end(12);
b.set_margin_top(6);
b.set_margin_bottom(6);
- user_box.pack_start(&username, true, true, 0);
- user_box.pack_start(&uid, false, false, 0);
+ user_box.pack_start(&username_box, true, true, 0);
+ user_box.pack_start(&uid, true, true, 0);
/* we don't have this state yet
* let state = gtk::Label::new();
* user_box.pack_end(&state, true, true, 0); */
b.pack_start(&avatar, false, true, 0);
- b.pack_start(&user_box, false, true, 0);
+ b.pack_start(&user_box, true, true, 0);
b.show_all();
b
}
-fn add_rows(container: gtk::ListBox, members: Vec<Member>) -> Option<usize> {
+fn add_rows(
+ container: gtk::ListBox,
+ members: Vec<Member>,
+ power_levels: HashMap<String, i32>,
+) -> Option<usize> {
/* Load just enough members to fill atleast the visible list */
for member in members.iter() {
- container.insert(&create_row(member.clone())?, -1);
+ let power_level = match power_levels.get(&member.uid) {
+ Some(pl) => Some(*pl),
+ None => None,
+ };
+ container.insert(&create_row(member.clone(), power_level)?, -1);
}
None
}
diff --git a/fractal-gtk/src/widgets/message.rs b/fractal-gtk/src/widgets/message.rs
index f2c2c9e5..e01090a6 100644
--- a/fractal-gtk/src/widgets/message.rs
+++ b/fractal-gtk/src/widgets/message.rs
@@ -166,7 +166,13 @@ impl MessageBox {
let alias = msg.sender_name.clone();
let avatar = widgets::Avatar::avatar_new(Some(globals::MSG_ICON_SIZE));
- let data = avatar.circle(uid.clone(), alias.clone(), globals::MSG_ICON_SIZE);
+ let data = avatar.circle(
+ uid.clone(),
+ alias.clone(),
+ globals::MSG_ICON_SIZE,
+ None,
+ None,
+ );
if let Some(name) = alias {
self.username.set_text(&name);
} else {
diff --git a/fractal-gtk/src/widgets/mod.rs b/fractal-gtk/src/widgets/mod.rs
index 04c6bbf8..81b422f1 100644
--- a/fractal-gtk/src/widgets/mod.rs
+++ b/fractal-gtk/src/widgets/mod.rs
@@ -23,9 +23,8 @@ mod sourceview_entry;
pub use self::address::Address;
pub use self::address::AddressType;
pub use self::autocomplete::Autocomplete;
-pub use self::avatar::admin_badge;
-pub use self::avatar::AdminColor;
pub use self::avatar::Avatar;
+pub use self::avatar::AvatarBadgeColor;
pub use self::avatar::AvatarData;
pub use self::avatar::AvatarExt;
pub use self::divider::NewMessageDivider;
diff --git a/fractal-gtk/src/widgets/room.rs b/fractal-gtk/src/widgets/room.rs
index f60866fb..ccc960af 100644
--- a/fractal-gtk/src/widgets/room.rs
+++ b/fractal-gtk/src/widgets/room.rs
@@ -47,7 +47,7 @@ impl<'a> RoomBox<'a> {
let widget_box = gtk::Box::new(gtk::Orientation::Horizontal, 0);
let avatar = widgets::Avatar::avatar_new(Some(AVATAR_SIZE));
- avatar.circle(room.id.clone(), room.name.clone(), AVATAR_SIZE);
+ avatar.circle(room.id.clone(), room.name.clone(), AVATAR_SIZE, None, None);
widget_box.pack_start(&avatar, false, false, 18);
let details_box = gtk::Box::new(gtk::Orientation::Vertical, 6);
diff --git a/fractal-gtk/src/widgets/room_settings.rs b/fractal-gtk/src/widgets/room_settings.rs
index b983983c..927f3f8b 100644
--- a/fractal-gtk/src/widgets/room_settings.rs
+++ b/fractal-gtk/src/widgets/room_settings.rs
@@ -415,7 +415,13 @@ impl RoomSettings {
}
let image = widgets::Avatar::avatar_new(Some(100));
- let data = image.circle(self.room.id.clone(), self.room.name.clone(), 100);
+ let data = image.circle(
+ self.room.id.clone(),
+ self.room.name.clone(),
+ 100,
+ None,
+ None,
+ );
download_to_cache(self.backend.clone(), self.room.id.clone(), data);
if edit {
@@ -616,7 +622,8 @@ impl RoomSettings {
)
.as_str(),
);
- let list = widgets::MembersList::new(members.clone(), entry);
+ let list =
+ widgets::MembersList::new(members.clone(), self.room.power_levels.clone(), entry);
let w = list.create()?;
b.add(&w);
self.members_list = Some(list);
diff --git a/fractal-gtk/src/widgets/roomrow.rs b/fractal-gtk/src/widgets/roomrow.rs
index 4cb65ae7..3f471b0e 100644
--- a/fractal-gtk/src/widgets/roomrow.rs
+++ b/fractal-gtk/src/widgets/roomrow.rs
@@ -65,7 +65,7 @@ impl RoomRow {
notifications.hide();
}
- icon.circle(room.id.clone(), Some(name), ICON_SIZE);
+ icon.circle(room.id.clone(), Some(name), ICON_SIZE, None, None);
let rr = RoomRow {
room,
@@ -130,7 +130,7 @@ impl RoomRow {
let name = self.room.name.clone().unwrap_or("...".to_string());
self.icon
- .circle(self.room.id.clone(), Some(name), ICON_SIZE);
+ .circle(self.room.id.clone(), Some(name), ICON_SIZE, None, None);
}
pub fn widget(&self) -> gtk::ListBoxRow {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]