[fractal/fractal-next] sidebar: Improve items look



commit 29a98b2e2bddafc5bece081dc986997e9e4092ee
Author: Kévin Commaille <zecakeh tedomum fr>
Date:   Tue May 4 21:01:32 2021 +0200

    sidebar: Improve items look

 data/resources/resources.gresource.xml    |   1 +
 data/resources/style.css                  |  34 +++++--
 data/resources/ui/sidebar-category-row.ui |  32 ++++++
 data/resources/ui/sidebar-item.ui         |  11 +--
 data/resources/ui/sidebar-room-row.ui     |   2 +-
 src/session/sidebar/category_row.rs       | 158 ++++++++++++++++++++++++++++++
 src/session/sidebar/mod.rs                |   2 +
 src/session/sidebar/row.rs                |  74 +++++++++-----
 src/session/sidebar/sidebar.rs            |  23 ++++-
 9 files changed, 295 insertions(+), 42 deletions(-)
---
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 499c665b..037753ad 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -11,6 +11,7 @@
     <file compressed="true" preprocess="xml-stripblanks" alias="session.ui">ui/session.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="sidebar.ui">ui/sidebar.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="sidebar-item.ui">ui/sidebar-item.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-category-row.ui">ui/sidebar-category-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="sidebar-room-row.ui">ui/sidebar-room-row.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" alias="window.ui">ui/window.ui</file>
     <file compressed="true" preprocess="xml-stripblanks" 
alias="context-menu-bin.ui">ui/context-menu-bin.ui</file>
diff --git a/data/resources/style.css b/data/resources/style.css
index a3218d67..335ee9e0 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -21,21 +21,39 @@
 }
 
 /* Sidebar */
-.sidebar row .dim-label {  
-  padding: 6px 12px;
+.sidebar row {
+  padding-left: 10px;
+  padding-right: 10px;
+}
+
+.sidebar .category {
+  margin-top: 4px;
   font-size: 0.8em;
   font-weight: bold;
 }
 
-.sidebar row .bold {
-  font-weight: bold;
+.sidebar .category image.arrow {
+  transition: 200ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
+}
+
+.sidebar .category .category-row:not(:checked) image.arrow:dir(ltr) {
+  transform: rotate(-0.25turn);
 }
 
-.sidebar indent  {
- -gtk-icon-size: 0px;
+.sidebar .category .category-row:not(:checked) image.arrow:dir(rtl) {
+  transform: rotate(0.25turn);
+}
+
+.sidebar .room {
+  padding-top: 4px;
+  padding-bottom: 4px;
+}
+
+.sidebar .room .bold {
+  font-weight: bold;
 }
 
-.sidebar row .notification_count {
+.sidebar .room .notification_count {
   /* TODO: use correct color variable */
   background-color: #555;
   color: white;
@@ -46,7 +64,7 @@
   padding: 2px 5px;
 }
 
-.sidebar row .highlight {
+.sidebar .room .highlight {
   /* TODO: use correct color variable */
   background-color: @theme_selected_bg_color;
 }
diff --git a/data/resources/ui/sidebar-category-row.ui b/data/resources/ui/sidebar-category-row.ui
new file mode 100644
index 00000000..ef3607bd
--- /dev/null
+++ b/data/resources/ui/sidebar-category-row.ui
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <template class="SidebarCategoryRow" parent="AdwBin">
+    <style>
+      <class name="category-row"/>
+    </style>
+    <child>
+      <object class="GtkBox">
+        <property name="spacing">12</property>
+        <child>
+          <object class="GtkLabel" id="display_name">
+            <property name="halign">start</property>
+            <property name="hexpand">True</property>
+            <property name="ellipsize">end</property>
+            <style>
+              <class name="dim-label"/>
+            </style>
+          </object>
+        </child>
+        <child type="end">
+          <object class="GtkImage" id="arrow">
+            <property name="icon-name">adw-expander-arrow-symbolic</property>
+            <style>
+              <class name="arrow"/>
+            </style>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
+
diff --git a/data/resources/ui/sidebar-item.ui b/data/resources/ui/sidebar-item.ui
index 8697f3d0..8bbc4c63 100644
--- a/data/resources/ui/sidebar-item.ui
+++ b/data/resources/ui/sidebar-item.ui
@@ -2,19 +2,10 @@
 <interface>
   <template class="GtkListItem">
     <property name="child">
-      <!-- TODO: we need to implement our own Expander: 
https://gitlab.gnome.org/GNOME/fractal/-/issues/749-->
-      <object class="GtkTreeExpander" id="expander">
+      <object class="SidebarRow">
         <binding name="list-row">
           <lookup name="item">GtkListItem</lookup>
         </binding>
-        <property name="child">
-          <object class="SidebarRow">
-            <property name="hexpand">True</property>
-            <binding name="item">
-                <lookup name="item">expander</lookup>
-            </binding>
-          </object>
-        </property>
       </object>
     </property>
   </template>
diff --git a/data/resources/ui/sidebar-room-row.ui b/data/resources/ui/sidebar-room-row.ui
index 7adad266..d5060291 100644
--- a/data/resources/ui/sidebar-room-row.ui
+++ b/data/resources/ui/sidebar-room-row.ui
@@ -3,7 +3,7 @@
   <template class="SidebarRoomRow" parent="AdwBin">
     <child>
       <object class="GtkBox">
-        <property name="spacing">6</property>
+        <property name="spacing">12</property>
         <child>
           <object class="AdwAvatar" id="avatar">
             <property name="show-initials">True</property>
diff --git a/src/session/sidebar/category_row.rs b/src/session/sidebar/category_row.rs
new file mode 100644
index 00000000..4706776e
--- /dev/null
+++ b/src/session/sidebar/category_row.rs
@@ -0,0 +1,158 @@
+use adw;
+use adw::subclass::prelude::BinImpl;
+use gtk::subclass::prelude::*;
+use gtk::{self, prelude::*};
+use gtk::{glib, CompositeTemplate};
+
+use crate::session::categories::Category;
+
+mod imp {
+    use super::*;
+    use glib::subclass::InitializingObject;
+    use std::cell::{Cell, RefCell};
+
+    #[derive(Debug, Default, CompositeTemplate)]
+    #[template(resource = "/org/gnome/FractalNext/sidebar-category-row.ui")]
+    pub struct CategoryRow {
+        pub category: RefCell<Option<Category>>,
+        pub expanded: Cell<bool>,
+        pub binding: RefCell<Option<glib::Binding>>,
+        #[template_child]
+        pub display_name: TemplateChild<gtk::Label>,
+    }
+
+    #[glib::object_subclass]
+    impl ObjectSubclass for CategoryRow {
+        const NAME: &'static str = "SidebarCategoryRow";
+        type Type = super::CategoryRow;
+        type ParentType = adw::Bin;
+
+        fn class_init(klass: &mut Self::Class) {
+            Self::bind_template(klass);
+        }
+
+        fn instance_init(obj: &InitializingObject<Self>) {
+            obj.init_template();
+        }
+    }
+
+    impl ObjectImpl for CategoryRow {
+        fn properties() -> &'static [glib::ParamSpec] {
+            use once_cell::sync::Lazy;
+            static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
+                vec![
+                    glib::ParamSpec::new_object(
+                        "category",
+                        "Category",
+                        "The category of this row",
+                        Category::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                    glib::ParamSpec::new_boolean(
+                        "expanded",
+                        "Expanded",
+                        "The expanded state of this row",
+                        true,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                ]
+            });
+
+            PROPERTIES.as_ref()
+        }
+
+        fn set_property(
+            &self,
+            obj: &Self::Type,
+            _id: usize,
+            value: &glib::Value,
+            pspec: &glib::ParamSpec,
+        ) {
+            match pspec.name() {
+                "category" => {
+                    let category = value.get().unwrap();
+                    obj.set_category(category);
+                }
+                "expanded" => {
+                    let expanded = value.get().unwrap();
+                    obj.set_expanded(expanded);
+                }
+                _ => unimplemented!(),
+            }
+        }
+
+        fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
+            match pspec.name() {
+                "category" => obj.category().to_value(),
+                "expanded" => obj.expanded().to_value(),
+                _ => unimplemented!(),
+            }
+        }
+    }
+
+    impl WidgetImpl for CategoryRow {}
+    impl BinImpl for CategoryRow {}
+}
+
+glib::wrapper! {
+    pub struct CategoryRow(ObjectSubclass<imp::CategoryRow>)
+        @extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
+}
+
+impl CategoryRow {
+    pub fn new() -> Self {
+        glib::Object::new(&[]).expect("Failed to create CategoryRow")
+    }
+
+    pub fn category(&self) -> Option<Category> {
+        let priv_ = imp::CategoryRow::from_instance(&self);
+        priv_.category.borrow().clone()
+    }
+
+    pub fn set_category(&self, category: Option<Category>) {
+        let priv_ = imp::CategoryRow::from_instance(&self);
+
+        if self.category() == category {
+            return;
+        }
+
+        if let Some(binding) = priv_.binding.take() {
+            binding.unbind();
+        }
+
+        if let Some(ref category) = category {
+            let binding = category
+                .bind_property("display-name", &priv_.display_name.get(), "label")
+                .flags(glib::BindingFlags::SYNC_CREATE)
+                .build()
+                .unwrap();
+
+            priv_.binding.replace(Some(binding));
+        }
+
+        priv_.category.replace(category);
+        self.notify("category");
+    }
+
+    fn expanded(&self) -> bool {
+        let priv_ = imp::CategoryRow::from_instance(&self);
+        priv_.expanded.get()
+    }
+
+    fn set_expanded(&self, expanded: bool) {
+        let priv_ = imp::CategoryRow::from_instance(&self);
+
+        if self.expanded() == expanded {
+            return;
+        }
+
+        if expanded {
+            self.set_state_flags(gtk::StateFlags::CHECKED, false);
+        } else {
+            self.unset_state_flags(gtk::StateFlags::CHECKED);
+        }
+
+        priv_.expanded.set(expanded);
+        self.notify("expanded");
+    }
+}
diff --git a/src/session/sidebar/mod.rs b/src/session/sidebar/mod.rs
index 17e19937..34b3697a 100644
--- a/src/session/sidebar/mod.rs
+++ b/src/session/sidebar/mod.rs
@@ -1,7 +1,9 @@
+mod category_row;
 mod room_row;
 mod row;
 mod sidebar;
 
+use self::category_row::CategoryRow;
 use self::room_row::RoomRow;
 use self::row::Row;
 pub use self::sidebar::Sidebar;
diff --git a/src/session/sidebar/row.rs b/src/session/sidebar/row.rs
index d0845c4c..6cf0c7e2 100644
--- a/src/session/sidebar/row.rs
+++ b/src/session/sidebar/row.rs
@@ -1,7 +1,7 @@
 use adw::{subclass::prelude::BinImpl, BinExt};
 use gtk::{glib, prelude::*, subclass::prelude::*};
 
-use crate::session::sidebar::RoomRow;
+use crate::session::sidebar::{CategoryRow, RoomRow};
 use crate::session::{categories::Category, room::Room};
 
 mod imp {
@@ -11,7 +11,7 @@ mod imp {
 
     #[derive(Debug, Default)]
     pub struct Row {
-        pub item: RefCell<Option<glib::Object>>,
+        pub list_row: RefCell<Option<gtk::TreeListRow>>,
         pub binding: RefCell<Option<glib::Binding>>,
     }
 
@@ -25,13 +25,22 @@ mod imp {
     impl ObjectImpl for Row {
         fn properties() -> &'static [glib::ParamSpec] {
             static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
-                vec![glib::ParamSpec::new_object(
-                    "item",
-                    "Item",
-                    "The sidebar item of this row",
-                    glib::Object::static_type(),
-                    glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
-                )]
+                vec![
+                    glib::ParamSpec::new_object(
+                        "item",
+                        "Item",
+                        "The sidebar item of this row",
+                        glib::Object::static_type(),
+                        glib::ParamFlags::READABLE,
+                    ),
+                    glib::ParamSpec::new_object(
+                        "list-row",
+                        "List Row",
+                        "The list row to track for expander state",
+                        gtk::TreeListRow::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
+                ]
             });
 
             PROPERTIES.as_ref()
@@ -45,9 +54,9 @@ mod imp {
             pspec: &glib::ParamSpec,
         ) {
             match pspec.name() {
-                "item" => {
-                    let item = value.get().unwrap();
-                    obj.set_item(item);
+                "list-row" => {
+                    let list_row = value.get().unwrap();
+                    obj.set_list_row(list_row);
                 }
                 _ => unimplemented!(),
             }
@@ -56,6 +65,7 @@ mod imp {
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "item" => obj.item().to_value(),
+                "list-row" => obj.list_row().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -76,14 +86,18 @@ impl Row {
     }
 
     pub fn item(&self) -> Option<glib::Object> {
+        self.list_row().and_then(|r| r.item())
+    }
+
+    pub fn list_row(&self) -> Option<gtk::TreeListRow> {
         let priv_ = imp::Row::from_instance(&self);
-        priv_.item.borrow().clone()
+        priv_.list_row.borrow().clone()
     }
 
-    pub fn set_item(&self, item: Option<glib::Object>) {
+    pub fn set_list_row(&self, list_row: Option<gtk::TreeListRow>) {
         let priv_ = imp::Row::from_instance(&self);
 
-        if self.item() == item {
+        if self.list_row() == list_row {
             return;
         }
 
@@ -91,26 +105,36 @@ impl Row {
             binding.unbind();
         }
 
-        if let Some(item) = item {
+        let row = if let Some(row) = list_row.clone() {
+            priv_.list_row.replace(list_row.clone());
+            row
+        } else {
+            return;
+        };
+
+        if let Some(item) = self.item() {
             if let Some(category) = item.downcast_ref::<Category>() {
                 let child =
-                    if let Some(Ok(child)) = self.child().map(|w| w.downcast::<gtk::Label>()) {
+                    if let Some(Ok(child)) = self.child().map(|w| w.downcast::<CategoryRow>()) {
                         child
                     } else {
-                        let child = gtk::Label::new(None);
+                        let child = CategoryRow::new();
                         self.set_child(Some(&child));
-                        self.set_halign(gtk::Align::Start);
-                        child.add_css_class("dim-label");
                         child
                     };
+                child.set_category(Some(category.clone()));
 
-                let binding = category
-                    .bind_property("display-name", &child, "label")
+                let binding = row
+                    .bind_property("expanded", &child, "expanded")
                     .flags(glib::BindingFlags::SYNC_CREATE)
                     .build()
                     .unwrap();
 
                 priv_.binding.replace(Some(binding));
+
+                if let Some(list_item) = self.parent() {
+                    list_item.set_css_classes(&["category"]);
+                }
             } else if let Some(room) = item.downcast_ref::<Room>() {
                 let child = if let Some(Ok(child)) = self.child().map(|w| w.downcast::<RoomRow>()) {
                     child
@@ -121,10 +145,16 @@ impl Row {
                 };
 
                 child.set_room(Some(room.clone()));
+
+                if let Some(list_item) = self.parent() {
+                    list_item.set_css_classes(&["room"]);
+                }
             } else {
                 panic!("Wrong row item: {:?}", item);
             }
         }
+
         self.notify("item");
+        self.notify("list-row");
     }
 }
diff --git a/src/session/sidebar/sidebar.rs b/src/session/sidebar/sidebar.rs
index 020ebd65..07caf238 100644
--- a/src/session/sidebar/sidebar.rs
+++ b/src/session/sidebar/sidebar.rs
@@ -2,7 +2,7 @@ use adw::subclass::prelude::BinImpl;
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*, CompositeTemplate};
 
 use crate::session::{
-    categories::Categories,
+    categories::{Categories, Category},
     room::Room,
     sidebar::{RoomRow, Row},
 };
@@ -105,6 +105,27 @@ mod imp {
                 _ => unimplemented!(),
             }
         }
+
+        fn constructed(&self, obj: &Self::Type) {
+            self.parent_constructed(obj);
+
+            self.listview.get().connect_activate(move |listview, pos| {
+                if let Some(row) = listview
+                    .model()
+                    .and_then(|m| m.downcast::<gtk::SingleSelection>().ok())
+                    .and_then(|m| m.item(pos))
+                    .and_then(|o| o.downcast::<gtk::TreeListRow>().ok())
+                {
+                    if row
+                        .item()
+                        .and_then(|o| o.downcast::<Category>().ok())
+                        .is_some()
+                    {
+                        row.set_expanded(!row.is_expanded());
+                    }
+                }
+            });
+        }
     }
 
     impl WidgetImpl for Sidebar {}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]