[fractal] fractal-gtk: Improve list keyboard navigation



commit 408c7ce2da96efae9619d45fb265998ff8cd67a4
Author: Christopher Davis <brainblasted disroot org>
Date:   Thu May 2 20:51:06 2019 -0400

    fractal-gtk: Improve list keyboard navigation
    
    Previously on both the room list and the message list,
    our GtkScrolledWindows would not scroll with our focus.
    This meant users that relied on keyboard navigation were
    stuck at the top of our room list and the bottom of our
    message list.
    
    Now we scroll both lists as focus moves, improving
    navigation for keyboard users.

 fractal-gtk/res/ui/main_window.ui        |   2 +-
 fractal-gtk/src/appop/mod.rs             |   2 +-
 fractal-gtk/src/appop/room.rs            |  14 ++++-
 fractal-gtk/src/widgets/roomlist.rs      | 101 ++++++++++++++++++++++++++++++-
 fractal-gtk/src/widgets/scroll_widget.rs |   8 +++
 5 files changed, 123 insertions(+), 4 deletions(-)
---
diff --git a/fractal-gtk/res/ui/main_window.ui b/fractal-gtk/res/ui/main_window.ui
index e4932e73..1cac9479 100644
--- a/fractal-gtk/res/ui/main_window.ui
+++ b/fractal-gtk/res/ui/main_window.ui
@@ -51,7 +51,7 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkScrolledWindow">
+                  <object class="GtkScrolledWindow" id="roomlist_scroll">
                     <property name="width_request">200</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
diff --git a/fractal-gtk/src/appop/mod.rs b/fractal-gtk/src/appop/mod.rs
index af5fd931..7553db6b 100644
--- a/fractal-gtk/src/appop/mod.rs
+++ b/fractal-gtk/src/appop/mod.rs
@@ -102,7 +102,7 @@ impl AppOp {
             msg_queue: vec![],
             sending_message: false,
             state: AppState::Login,
-            roomlist: widgets::RoomList::new(None),
+            roomlist: widgets::RoomList::new(None, None),
             since: None,
             unsent_messages: HashMap::new(),
 
diff --git a/fractal-gtk/src/appop/room.rs b/fractal-gtk/src/appop/room.rs
index d652dce6..418d413d 100644
--- a/fractal-gtk/src/appop/room.rs
+++ b/fractal-gtk/src/appop/room.rs
@@ -90,7 +90,19 @@ impl AppOp {
                 container.remove(ch);
             }
 
-            self.roomlist = widgets::RoomList::new(Some(self.server_url.clone()));
+            let scrolledwindow: gtk::ScrolledWindow = self
+                .ui
+                .builder
+                .get_object("roomlist_scroll")
+                .expect("Couldn't find room_container in ui file.");
+            let adj = scrolledwindow.get_vadjustment();
+            scrolledwindow.get_child().map(|child| {
+                child.downcast_ref::<gtk::Container>().map(|container| {
+                    adj.clone().map(|a| container.set_focus_vadjustment(&a));
+                });
+            });
+
+            self.roomlist = widgets::RoomList::new(adj, Some(self.server_url.clone()));
             self.roomlist.add_rooms(roomlist);
             container.add(self.roomlist.widget());
 
diff --git a/fractal-gtk/src/widgets/roomlist.rs b/fractal-gtk/src/widgets/roomlist.rs
index 0cb9a2f9..c1158cbc 100644
--- a/fractal-gtk/src/widgets/roomlist.rs
+++ b/fractal-gtk/src/widgets/roomlist.rs
@@ -50,6 +50,13 @@ impl RoomUpdated {
     }
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum RoomListType {
+    Invites,
+    Rooms,
+    Favorites,
+}
+
 pub struct RoomListGroup {
     pub rooms: HashMap<String, RoomRow>,
     pub baseu: Url,
@@ -396,6 +403,7 @@ impl RGroup {
 pub struct RoomList {
     pub baseu: Url,
     widget: gtk::Box,
+    adj: Option<gtk::Adjustment>,
 
     inv: RGroup,
     fav: RGroup,
@@ -415,7 +423,7 @@ macro_rules! run_in_group {
 }
 
 impl RoomList {
-    pub fn new(url: Option<String>) -> RoomList {
+    pub fn new(adj: Option<gtk::Adjustment>, url: Option<String>) -> RoomList {
         let widget = gtk::Box::new(gtk::Orientation::Vertical, 6);
         let baseu = get_url(url);
 
@@ -438,6 +446,7 @@ impl RoomList {
         let rl = RoomList {
             baseu,
             widget,
+            adj,
             inv,
             fav,
             rooms,
@@ -556,6 +565,7 @@ impl RoomList {
         self.widget.add(self.fav.get().widget());
         self.widget.add(self.rooms.get().widget());
         self.connect_select();
+        self.connect_keynav();
 
         self.show_and_hide();
 
@@ -627,9 +637,98 @@ impl RoomList {
         });
     }
 
+    pub fn connect_keynav(&self) {
+        let weak_inv_lb = self.inv.get().list.downgrade();
+        let weak_fav_lb = self.fav.get().list.downgrade();
+        let weak_room_lb = self.rooms.get().list.downgrade();
+        let adj = self.adj.clone();
+        let type_ = RoomListType::Invites;
+        self.inv.get().list.connect_keynav_failed(move |_, d| {
+            let inv_lb = upgrade_weak!(weak_inv_lb, gtk::Inhibit(false));
+            let fav_lb = upgrade_weak!(weak_fav_lb, gtk::Inhibit(false));
+            let room_lb = upgrade_weak!(weak_room_lb, gtk::Inhibit(false));
+
+            keynav_cb(d, &inv_lb, &fav_lb, &room_lb, adj.clone(), type_)
+        });
+
+        let weak_fav_lb = self.fav.get().list.downgrade();
+        let weak_inv_lb = self.inv.get().list.downgrade();
+        let weak_room_lb = self.rooms.get().list.downgrade();
+        let adj = self.adj.clone();
+        let type_ = RoomListType::Favorites;
+        self.fav.get().list.connect_keynav_failed(move |_, d| {
+            let fav_lb = upgrade_weak!(weak_fav_lb, gtk::Inhibit(false));
+            let inv_lb = upgrade_weak!(weak_inv_lb, gtk::Inhibit(false));
+            let room_lb = upgrade_weak!(weak_room_lb, gtk::Inhibit(false));
+
+            keynav_cb(d, &inv_lb, &fav_lb, &room_lb, adj.clone(), type_)
+        });
+
+        let weak_rooms_lb = self.rooms.get().list.downgrade();
+        let weak_inv_lb = self.inv.get().list.downgrade();
+        let weak_fav_lb = self.fav.get().list.downgrade();
+        let adj = self.adj.clone();
+        let type_ = RoomListType::Rooms;
+        self.rooms.get().list.connect_keynav_failed(move |_, d| {
+            let rooms_lb = upgrade_weak!(weak_rooms_lb, gtk::Inhibit(false));
+            let inv_lb = upgrade_weak!(weak_inv_lb, gtk::Inhibit(false));
+            let fav_lb = upgrade_weak!(weak_fav_lb, gtk::Inhibit(false));
+
+            keynav_cb(d, &inv_lb, &fav_lb, &rooms_lb, adj.clone(), type_)
+        });
+    }
+
     pub fn filter_rooms(&self, term: Option<String>) {
         self.inv.get().filter_rooms(&term);
         self.fav.get().filter_rooms(&term);
         self.rooms.get().filter_rooms(&term);
     }
 }
+
+/// Navigates between the different room
+/// lists seamlessly with widget focus,
+/// while keeping the `gtk::ScrolledWindow` in
+/// the proper position.
+///
+/// Translated from https://gitlab.gnome.org/GNOME/gtk/blob/d3ad6425/gtk/inspector/general.c#L655
+fn keynav_cb(
+    direction: gtk::DirectionType,
+    inv_lb: &gtk::ListBox,
+    fav_lb: &gtk::ListBox,
+    room_lb: &gtk::ListBox,
+    adj: Option<gtk::Adjustment>,
+    type_: RoomListType,
+) -> gtk::Inhibit {
+    let next: Option<&gtk::ListBox>;
+    next = match (direction, type_) {
+        (gtk::DirectionType::Down, RoomListType::Invites) => Some(fav_lb),
+        (gtk::DirectionType::Down, RoomListType::Favorites) => Some(room_lb),
+        (gtk::DirectionType::Up, RoomListType::Rooms) => Some(fav_lb),
+        (gtk::DirectionType::Up, RoomListType::Favorites) => Some(inv_lb),
+        _ => None,
+    };
+
+    if let Some(widget) = next {
+        widget.child_focus(direction);
+        gtk::Inhibit(true)
+    } else if let Some(adjustment) = adj {
+        let value = adjustment.get_value();
+        let lower = adjustment.get_lower();
+        let upper = adjustment.get_upper();
+        let page = adjustment.get_page_size();
+
+        match direction {
+            gtk::DirectionType::Up if value > lower => {
+                adjustment.set_value(lower);
+                gtk::Inhibit(true)
+            }
+            gtk::DirectionType::Down if value < upper - page => {
+                adjustment.set_value(upper - page);
+                gtk::Inhibit(true)
+            }
+            _ => gtk::Inhibit(false),
+        }
+    } else {
+        gtk::Inhibit(false)
+    }
+}
diff --git a/fractal-gtk/src/widgets/scroll_widget.rs b/fractal-gtk/src/widgets/scroll_widget.rs
index 031d8e07..f848384b 100644
--- a/fractal-gtk/src/widgets/scroll_widget.rs
+++ b/fractal-gtk/src/widgets/scroll_widget.rs
@@ -100,6 +100,14 @@ impl Widgets {
             .add_class("messages-box");
         container.add(&column);
 
+        view.get_vadjustment().map(|adj| {
+            view.get_child().map(|child| {
+                child.downcast_ref::<gtk::Container>().map(|container| {
+                    container.set_focus_vadjustment(&adj);
+                });
+            });
+        });
+
         /* add a load more Spinner */
         let spinner = gtk::Spinner::new();
         messages.add(&create_load_more_spn(&spinner));


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