gardesk/garfield / 563cee8

Browse files

sidebar: add Recents entry and SidebarClick enum

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
563cee8ec0113aff480fc64919a2e37a51e170fb
Parents
a76e303
Tree
47f9929

1 changed file

StatusFile+-
M garfield/src/ui/sidebar.rs 108 20
garfield/src/ui/sidebar.rsmodified
@@ -6,6 +6,15 @@ use std::fs;
66
 use std::io::{BufRead, BufReader, Write};
77
 use std::path::{Path, PathBuf};
88
 
9
+/// Result of clicking on a sidebar item.
10
+#[derive(Debug, Clone)]
11
+pub enum SidebarClick {
12
+    /// Navigate to a path (place or bookmark).
13
+    Path(PathBuf),
14
+    /// Open the Recents view.
15
+    Recents,
16
+}
17
+
918
 /// A place or bookmark in the sidebar.
1019
 #[derive(Debug, Clone)]
1120
 pub struct Place {
@@ -29,7 +38,7 @@ pub struct Sidebar {
2938
     bookmarks: Vec<Place>,
3039
     /// Component bounds.
3140
     bounds: Rect,
32
-    /// Hovered item index (in combined list).
41
+    /// Hovered item index (in combined list, 0 = Recents).
3342
     hovered: Option<usize>,
3443
     /// Whether sidebar is visible.
3544
     visible: bool,
@@ -51,6 +60,8 @@ pub struct Sidebar {
5160
     bookmark_drag_start: Option<Point>,
5261
     /// Whether bookmark drag is active (past threshold).
5362
     bookmark_drag_active: bool,
63
+    /// Bounds of the Recents entry for hit testing.
64
+    recents_bounds: Rect,
5465
 }
5566
 
5667
 impl Sidebar {
@@ -76,6 +87,7 @@ impl Sidebar {
7687
             bookmark_drop_index: None,
7788
             bookmark_drag_start: None,
7889
             bookmark_drag_active: false,
90
+            recents_bounds: Rect::new(0, 0, 0, 0),
7991
         };
8092
         sidebar.populate_default_places();
8193
         sidebar.load_bookmarks();
@@ -524,27 +536,32 @@ impl Sidebar {
524536
         self.visible
525537
     }
526538
 
527
-    /// Total number of items (places + bookmarks + separator if bookmarks exist).
539
+    /// Total number of items (Recents + places + bookmarks).
540
+    /// Index 0 is Recents, 1..=places.len() are places, rest are bookmarks.
528541
     fn total_items(&self) -> usize {
529
-        self.places.len() + self.bookmarks.len()
542
+        1 + self.places.len() + self.bookmarks.len()
530543
     }
531544
 
532
-    /// Get item by combined index.
545
+    /// Get item by combined index (0 = Recents which returns None).
533546
     fn get_item(&self, index: usize) -> Option<&Place> {
534
-        if index < self.places.len() {
535
-            self.places.get(index)
547
+        if index == 0 {
548
+            // Recents is special, not a Place
549
+            None
550
+        } else if index <= self.places.len() {
551
+            self.places.get(index - 1)
536552
         } else {
537
-            self.bookmarks.get(index - self.places.len())
553
+            self.bookmarks.get(index - 1 - self.places.len())
538554
         }
539555
     }
540556
 
541557
     /// Get mutable item by combined index.
542558
     fn get_item_mut(&mut self, index: usize) -> Option<&mut Place> {
543
-        let places_len = self.places.len();
544
-        if index < places_len {
545
-            self.places.get_mut(index)
559
+        if index == 0 {
560
+            None
561
+        } else if index <= self.places.len() {
562
+            self.places.get_mut(index - 1)
546563
         } else {
547
-            self.bookmarks.get_mut(index - places_len)
564
+            self.bookmarks.get_mut(index - 1 - self.places.len())
548565
         }
549566
     }
550567
 
@@ -558,7 +575,15 @@ impl Sidebar {
558575
         }
559576
 
560577
         self.hovered = None;
561
-        for i in 0..self.total_items() {
578
+
579
+        // Check Recents entry first (index 0)
580
+        if self.recents_bounds.contains_point(pos) {
581
+            self.hovered = Some(0);
582
+            return self.hovered != old_hovered;
583
+        }
584
+
585
+        // Check places and bookmarks
586
+        for i in 1..self.total_items() {
562587
             if let Some(place) = self.get_item(i) {
563588
                 if place.bounds.contains_point(pos) {
564589
                     self.hovered = Some(i);
@@ -576,16 +601,22 @@ impl Sidebar {
576601
         false // Sidebar doesn't scroll currently
577602
     }
578603
 
579
-    /// Handle mouse click. Returns the path to navigate to, if any.
580
-    pub fn on_click(&self, pos: Point) -> Option<PathBuf> {
604
+    /// Handle mouse click. Returns the clicked item.
605
+    pub fn on_click(&self, pos: Point) -> Option<SidebarClick> {
581606
         if !self.visible || !self.bounds.contains_point(pos) {
582607
             return None;
583608
         }
584609
 
585
-        for i in 0..self.total_items() {
610
+        // Check Recents entry first
611
+        if self.recents_bounds.contains_point(pos) {
612
+            return Some(SidebarClick::Recents);
613
+        }
614
+
615
+        // Check places and bookmarks
616
+        for i in 1..self.total_items() {
586617
             if let Some(place) = self.get_item(i) {
587618
                 if place.bounds.contains_point(pos) {
588
-                    return Some(place.path.clone());
619
+                    return Some(SidebarClick::Path(place.path.clone()));
589620
                 }
590621
             }
591622
         }
@@ -662,10 +693,27 @@ impl Sidebar {
662693
 
663694
         let mut y = self.bounds.y + self.padding as i32;
664695
 
665
-        // Render places
696
+        // Render Recents entry (always at top)
697
+        let is_recents_hovered = self.hovered == Some(0);
698
+        y = self.render_recents(renderer, y, is_recents_hovered, &icon_style, &name_style, &hover_style)?;
699
+
700
+        // Separator after Recents
701
+        y += 4;
702
+        renderer.line(
703
+            (self.bounds.x + self.padding as i32) as f64,
704
+            y as f64,
705
+            (self.bounds.x + self.bounds.width as i32 - self.padding as i32) as f64,
706
+            y as f64,
707
+            theme.border,
708
+            1.0,
709
+        )?;
710
+        y += 8;
711
+
712
+        // Render places (indices 1..=places.len())
666713
         for i in 0..self.places.len() {
667
-            let is_hovered = self.hovered == Some(i);
668
-            y = self.render_item(renderer, i, y, is_hovered, &icon_style, &name_style, &hover_style)?;
714
+            let combined_index = i + 1; // Offset by 1 for Recents
715
+            let is_hovered = self.hovered == Some(combined_index);
716
+            y = self.render_item(renderer, combined_index, y, is_hovered, &icon_style, &name_style, &hover_style)?;
669717
         }
670718
 
671719
         // Always show separator and bookmarks section
@@ -718,7 +766,8 @@ impl Sidebar {
718766
                     self.render_drop_indicator(renderer, y)?;
719767
                 }
720768
 
721
-                let combined_index = self.places.len() + i;
769
+                // Offset by 1 (Recents) + places.len()
770
+                let combined_index = 1 + self.places.len() + i;
722771
                 let is_hovered = self.hovered == Some(combined_index);
723772
                 let is_dragging = self.bookmark_drag_index == Some(i);
724773
                 y = self.render_bookmark_item(renderer, combined_index, y, is_hovered, is_dragging, &icon_style, &name_style, &hover_style)?;
@@ -859,4 +908,43 @@ impl Sidebar {
859908
         )?;
860909
         Ok(())
861910
     }
911
+
912
+    /// Render the Recents entry at the top of the sidebar.
913
+    fn render_recents(
914
+        &mut self,
915
+        renderer: &Renderer,
916
+        y: i32,
917
+        is_hovered: bool,
918
+        icon_style: &TextStyle,
919
+        name_style: &TextStyle,
920
+        hover_style: &TextStyle,
921
+    ) -> anyhow::Result<i32> {
922
+        let theme = renderer.theme();
923
+
924
+        // Update bounds for hit testing
925
+        self.recents_bounds = Rect::new(
926
+            self.bounds.x,
927
+            y,
928
+            self.bounds.width,
929
+            self.item_height,
930
+        );
931
+
932
+        // Draw hover background
933
+        if is_hovered {
934
+            renderer.fill_rect(self.recents_bounds, theme.item_background)?;
935
+        }
936
+
937
+        let text_style = if is_hovered { hover_style } else { name_style };
938
+
939
+        // Draw icon (clock symbol)
940
+        let icon_x = self.bounds.x + self.padding as i32;
941
+        let text_y = y + (self.item_height as i32 - theme.font_size as i32) / 2;
942
+        renderer.text("R", icon_x as f64, text_y as f64, icon_style)?;
943
+
944
+        // Draw name
945
+        let name_x = icon_x + 20;
946
+        renderer.text("Recents", name_x as f64, text_y as f64, text_style)?;
947
+
948
+        Ok(y + self.item_height as i32)
949
+    }
862950
 }