improving the breadcrumb
right now the breadcrumb interactively is not functional. as advertised we can click portions of the breadcrumb to jump to but every click is just registered as the active dir and so no jump is performed I think we ought to think like web devs and make a breadcrumb component
here's my vision for the
improved breadcrumb
- is color coded:
- gray/dim for inactive dirs in the path
- black for "/"
- bold red for active dir
- root should be green to indicate we should try to make it clickable
- though I imagine that's hard with the pixel adjustments
- is back/forwards aware
- if we previously navigated from a path say we went from ~/Downloads to ~/
- we expect the path to be /home/matthewwolffe/Downloads
- with Downlaods and home greyed out.
- we expect the path to be /home/matthewwolffe/Downloads
- if we previously navigated from a path say we went from ~/Downloads to ~/
- should look as similar to the current breadcrumb as possible. though maybe we should add hover state to dirs to indcate they can be clicked
- hover ideas
- darker gray for inactive dirs
- lose bold effect on active dir
- hover ideas
clear!? it may be that the vision is unattainable within a single text string, as in we can't detect fine tuned clicks like that let me know if this vision is impossible!
FEASIBILITY ASSESSMENT (2025-11-11)
Verdict: ✅ TOTALLY FEASIBLE - Vision is achievable with custom Cairo rendering
Approaches Considered:
Option 1: Separate GTK Buttons (Standard)
Pros:
- ✅ Easy (~100 lines of code)
- ✅ Automatic hover/click detection
- ✅ Built-in accessibility
- ✅ CSS styling support
- ✅ 3-4 hours implementation time
Cons:
- ❌ Button borders/padding may look less seamless
- ❌ Less control over exact appearance
Option 2: GtkLabel + Pango Hit-Testing (Hybrid)
Pros:
- ⚠️ No button visual artifacts
- ⚠️ Single text widget
Cons:
- ❌ Pango
xy_to_index()gives character indices, not semantic segments - ❌ Must manually parse
/delimiter positions - ❌ Complex hover state management (~250 lines)
- ❌ Poor accessibility
Option 3: Custom Cairo Rendering (Chosen) ✅
Pros:
- ✅ Complete control over appearance (exact vision match)
- ✅ Seamless floating text aesthetic
- ✅ Smooth hover effects (darker gray)
- ✅ Color coding exactly as specified
- ✅ Back/forward awareness (dim previously-visited segments)
- ✅ No button borders or padding artifacts
Cons:
- ⚠️ More code (~350 lines)
- ⚠️ Manual hit-testing required
- ⚠️ 2-3 days implementation time
WHY CAIRO?
Decision rationale:
- Achieves the vision exactly - No compromises on appearance
- Performance is negligible - Breadcrumb is tiny compared to treemap (5-10 text segments vs 100s-1000s of rectangles)
- State safety is already solved - GTK event loop serialization + cached path segments
- You're already using Cairo - Treemap widget proves you know the APIs
- Seamless look - Floating text with precise hover zones, no visual artifacts
Performance Considerations:
Impact: <0.1% CPU overhead
- Breadcrumb renders 5-10 text segments (trivial)
- Treemap renders 100s-1000s of rectangles (heavy) - breadcrumb is nothing by comparison
- Pango text rendering is GPU-accelerated
- Optimization: Only redraw on hover state change, not every mouse move
During scans:
- Zero overhead - breadcrumb only updates on navigation events
- Same thread-safety as current implementation (GTK main loop serialization)
- Cache path segments on main thread, draw function reads cached data
State Corruption Risk:
Assessment: Same as current implementation (safe with caching)
- Current
breadcrumb_callback()already accessescurrent_viewpointer - Protection via GTK event loop (all widget access on main thread)
- Progressive scanner uses
g_idle_add()for thread-safe callbacks - Strategy: Cache path segments when
breadcrumb_callback()fires, draw function reads cache only
IMPLEMENTATION PLAN
Phase 1: Create Custom Breadcrumb Widget Module
File: src/gui/breadcrumb_widget.f90
Tasks:
-
Create module skeleton with Cairo drawing area
-
Define cached state variables:
! Path segment cache (updated on main thread only) character(len=256), dimension(50), save :: cached_segments integer, save :: cached_segment_count = 0 ! Hover state integer, save :: hovered_segment = 0 ! 0 = none, 1+ = segment index integer, save :: last_hovered_segment = -1 ! Click bounds for each segment type :: segment_bounds integer :: x, y, width, height end type type(segment_bounds), dimension(50), save :: segment_rects -
Create widget initialization function:
function create_breadcrumb_widget() result(widget) type(c_ptr) :: widget ! Create GtkDrawingArea ! Set draw function ! Attach motion/click controllers end function
Estimated time: 2-3 hours
Phase 2: Path Parsing Logic
Tasks:
-
Split path into segments:
! Input: "/Users/matt/Documents/sniffly" ! Output: ! segments(1) = "/" ! segments(2) = "/Users" ! segments(3) = "/Users/matt" ! segments(4) = "/Users/matt/Documents" ! segments(5) = "/Users/matt/Documents/sniffly" ! (Full paths for click navigation, but display only last component) -
Handle ~ abbreviation:
! Replace /Users/matt with ~ for display ! But keep full path for navigation -
Extract display names:
! From "/Users/matt", display "matt" ! From "~/Documents", display "Documents" ! From "/", display "/" -
Create
update_breadcrumb_cache()function:subroutine update_breadcrumb_cache(full_path) character(len=*), intent(in) :: full_path ! Parse path into cached_segments ! Store full paths for navigation ! Trigger redraw end subroutine
Estimated time: 3-4 hours
Phase 3: Cairo Draw Function
Tasks:
-
Render each segment with Pango:
subroutine draw_breadcrumb(area, cr, width, height, user_data) bind(c) ! For each segment: ! 1. Determine color based on state: ! - Root (i==1): green ! - Active (i==count): bold red ! - Inactive: gray ! - Hovered inactive: darker gray ! 2. Set Cairo color ! 3. Draw text with Pango ! 4. Store bounds in segment_rects(i) ! 5. Draw separator " / " ! 6. Update x_offset end subroutine -
Color logic:
! Root segment (/) if (i == 1) then call cairo_set_source_rgb(cr, 0.0_c_double, 0.6_c_double, 0.0_c_double) ! Green ! Active segment (last in path) else if (i == cached_segment_count) then call cairo_set_source_rgb(cr, 0.8_c_double, 0.0_c_double, 0.0_c_double) ! Bold red font_desc = pango_font_description_from_string("Sans Bold 11"//c_null_char) ! Inactive segment (hovered) else if (i == hovered_segment) then call cairo_set_source_rgb(cr, 0.3_c_double, 0.3_c_double, 0.3_c_double) ! Darker gray ! Inactive segment (not hovered) else call cairo_set_source_rgb(cr, 0.5_c_double, 0.5_c_double, 0.5_c_double) ! Gray end if -
Back/forward awareness integration:
! Check if segment is in backwards history logical function is_backwards_segment(segment_path) ! Compare with nav_history and nav_history_pos ! If segment_path appears before current position, return true end function ! Apply extra dimming to backwards segments if (is_backwards_segment(cached_segments(i))) then call cairo_set_source_rgba(cr, 0.5_c_double, 0.5_c_double, 0.5_c_double, 0.6_c_double) ! Gray + transparency end if
Estimated time: 5-6 hours
Phase 4: Mouse Interaction
Tasks:
-
Hit-testing function:
function find_segment_at_position(x, y) result(segment_index) real(c_double), intent(in) :: x, y integer :: segment_index integer :: i segment_index = 0 do i = 1, cached_segment_count if (x >= segment_rects(i)%x .and. & x <= segment_rects(i)%x + segment_rects(i)%width .and. & y >= segment_rects(i)%y .and. & y <= segment_rects(i)%y + segment_rects(i)%height) then segment_index = i return end if end do end function -
Motion callback (hover):
subroutine on_breadcrumb_motion(controller, x, y, user_data) bind(c) integer :: new_hovered new_hovered = find_segment_at_position(x, y) ! Only redraw if hover state CHANGED if (new_hovered /= last_hovered_segment) then last_hovered_segment = new_hovered hovered_segment = new_hovered call gtk_widget_queue_draw(breadcrumb_widget_ptr) end if end subroutine -
Click callback (navigation):
subroutine on_breadcrumb_click(gesture, n_press, x, y, user_data) bind(c) integer :: clicked_segment, levels_up clicked_segment = find_segment_at_position(x, y) if (clicked_segment > 0 .and. clicked_segment < cached_segment_count) then ! Navigate to clicked segment levels_up = cached_segment_count - clicked_segment call navigate_up(levels_up) ! Trigger navigation callback to update history/UI if (associated(nav_callback)) call nav_callback() ! Redraw call gtk_widget_queue_draw(breadcrumb_widget_ptr) end if end subroutine
Estimated time: 3-4 hours
Phase 5: Integration with gtk_app.f90
Tasks:
-
Replace current breadcrumb bar:
- REMOVE lines 356-364 (old breadcrumb_bar creation)
- REMOVE
breadcrumb_box_ptrglobal variable - REMOVE
breadcrumb_buttonsarray - REMOVE
breadcrumb_counttracking
-
Add new breadcrumb widget:
! In on_activate(), after toolbar creation: use breadcrumb_widget, only: create_breadcrumb_widget, update_breadcrumb_cache type(c_ptr) :: breadcrumb_area ! Create custom breadcrumb widget breadcrumb_area = create_breadcrumb_widget() call gtk_widget_set_size_request(breadcrumb_area, -1_c_int, 30_c_int) ! Height: 30px call gtk_box_append(main_box, breadcrumb_area) -
Update breadcrumb_callback():
subroutine breadcrumb_callback() use breadcrumb_widget, only: update_breadcrumb_cache use treemap_renderer, only: get_current_view_node current_view => get_current_view_node() if (associated(current_view) .and. allocated(current_view%path)) then ! Update custom breadcrumb cache call update_breadcrumb_cache(current_view%path) end if ! Existing history/status logic... end subroutine -
CRITICAL: Unhook old click behavior:
- REMOVE
on_breadcrumb_clicked()function (lines 1328-1356) - REMOVE
sniffly_update_breadcrumbs()function (lines 1222-1325) - Navigation will now be handled by breadcrumb_widget's click callback
- REMOVE
-
Update meson.build:
sources += [ 'src/gui/breadcrumb_widget.f90', # ... existing sources ]
Estimated time: 2-3 hours
Phase 6: Back/Forward Awareness (Advanced Feature)
Tasks:
-
Expose navigation history to breadcrumb:
! In gtk_app.f90, add public getter: public :: get_navigation_history, get_navigation_position function get_navigation_history() result(history) character(len=512), dimension(:), allocatable :: history allocate(history(nav_history_count)) history = nav_history(1:nav_history_count) end function -
Check in breadcrumb draw function:
! In breadcrumb_widget.f90 draw function: use gtk_app, only: get_navigation_history, get_navigation_position character(len=512), dimension(:), allocatable :: history integer :: history_pos, j logical :: is_backwards history = get_navigation_history() history_pos = get_navigation_position() ! For each segment, check if it was in earlier history is_backwards = .false. do j = 1, history_pos - 1 ! Check all history before current if (index(trim(history(j)), trim(cached_segments(i))) > 0) then is_backwards = .true. exit end if end do ! Apply dimming if backwards if (is_backwards) then alpha = 0.6_c_double ! More transparent end if
Estimated time: 2-3 hours
Phase 7: Testing & Polish
Tasks:
-
Test scenarios:
- Navigate into nested directories - breadcrumb updates correctly
- Click middle segment - navigates up correct number of levels
- Hover over segments - color changes smoothly
- Click active (red) segment - no navigation (already there)
- Back/Forward navigation - backwards segments dim appropriately
- Rapid mouse movement - no excessive redraws
- Long paths - breadcrumb doesn't overflow window width
-
Handle edge cases:
- Root path
/- shows only one segment - Home path
~- abbreviates correctly - Very long paths - add ellipsis or scrolling?
- Window resize - breadcrumb reflows
- Root path
-
Performance validation:
- Run profiler during hover - verify <0.1% CPU
- Check redraw frequency - only on state changes
- Test during active scans - no interference
-
Visual polish:
- Adjust colors to match GTK theme
- Fine-tune separator spacing
- Ensure alignment with toolbar
Estimated time: 3-4 hours
TOTAL EFFORT ESTIMATE
- Phase 1: 2-3 hours (module setup)
- Phase 2: 3-4 hours (path parsing)
- Phase 3: 5-6 hours (Cairo drawing)
- Phase 4: 3-4 hours (mouse interaction)
- Phase 5: 2-3 hours (integration)
- Phase 6: 2-3 hours (back/forward awareness)
- Phase 7: 3-4 hours (testing & polish)
Total: 20-27 hours (~2-3 days of focused work)
CRITICAL NOTES
Don't Forget:
- Unhook old breadcrumb click behavior - Remove
on_breadcrumb_clicked()and related button logic - Remove old breadcrumb widgets - Clean up
breadcrumb_box_ptr,breadcrumb_buttons, etc. - Navigation callback integration - Breadcrumb widget needs access to
navigate_up()from treemap_renderer - Thread safety - Always update cache on main thread via callbacks, never in draw function
- Redraw optimization - Only queue redraw when hover STATE changes, not on every mouse move
Performance Gotchas:
- Cache path segments when navigation happens, not on every draw
- Use
last_hovered_segmentto detect state changes - Pango layout creation can be expensive - consider caching layouts between redraws if profiling shows issues
Future Enhancements:
- Tooltip on hover showing full path + size stats
- Keyboard navigation (Tab through segments, Enter to navigate)
- Context menu on right-click (same as treemap)
- Animated transitions when segments update
SUCCESS CRITERIA
✅ Vision achieved when:
- Breadcrumb shows path as seamless floating text (no button borders)
- Root segment is green
- Active (last) segment is bold red
- Inactive segments are gray
- Hovered segments darken
- Clicking any segment navigates to that level
- Back/forward navigation dims previously-visited segments
- Performance <0.1% CPU overhead
- No state corruption during scans
- Looks visually seamless and polished
View source
| 1 | # improving the breadcrumb |
| 2 | right now the breadcrumb interactively is not functional. |
| 3 | as advertised we can click portions of the breadcrumb to jump to |
| 4 | but every click is just registered as the active dir and so no jump is performed |
| 5 | I think we ought to think like web devs and make a breadcrumb component |
| 6 | |
| 7 | here's my vision for the |
| 8 | |
| 9 | ## improved breadcrumb |
| 10 | - is color coded: |
| 11 | - gray/dim for inactive dirs in the path |
| 12 | - black for "/" |
| 13 | - bold red for active dir |
| 14 | - root should be green to indicate we should try to make it clickable |
| 15 | - though I imagine that's hard with the pixel adjustments |
| 16 | - is back/forwards aware |
| 17 | - if we previously navigated from a path say we went from ~/Downloads to ~/ |
| 18 | - we expect the path to be /home/matthewwolffe/Downloads |
| 19 | - with Downlaods and home greyed out. |
| 20 | - should look as similar to the current breadcrumb as possible. though maybe we should add hover state to dirs to indcate they can be clicked |
| 21 | - hover ideas |
| 22 | - darker gray for inactive dirs |
| 23 | - lose bold effect on active dir |
| 24 | |
| 25 | clear!? |
| 26 | it may be that the vision is unattainable within a single text string, as in we can't detect fine tuned clicks like that |
| 27 | let me know if this vision is impossible! |
| 28 | |
| 29 | --- |
| 30 | |
| 31 | ## FEASIBILITY ASSESSMENT (2025-11-11) |
| 32 | |
| 33 | **Verdict:** ✅ **TOTALLY FEASIBLE** - Vision is achievable with custom Cairo rendering |
| 34 | |
| 35 | ### Approaches Considered: |
| 36 | |
| 37 | #### Option 1: Separate GTK Buttons (Standard) |
| 38 | **Pros:** |
| 39 | - ✅ Easy (~100 lines of code) |
| 40 | - ✅ Automatic hover/click detection |
| 41 | - ✅ Built-in accessibility |
| 42 | - ✅ CSS styling support |
| 43 | - ✅ 3-4 hours implementation time |
| 44 | |
| 45 | **Cons:** |
| 46 | - ❌ Button borders/padding may look less seamless |
| 47 | - ❌ Less control over exact appearance |
| 48 | |
| 49 | #### Option 2: GtkLabel + Pango Hit-Testing (Hybrid) |
| 50 | **Pros:** |
| 51 | - ⚠️ No button visual artifacts |
| 52 | - ⚠️ Single text widget |
| 53 | |
| 54 | **Cons:** |
| 55 | - ❌ Pango `xy_to_index()` gives character indices, not semantic segments |
| 56 | - ❌ Must manually parse `/` delimiter positions |
| 57 | - ❌ Complex hover state management (~250 lines) |
| 58 | - ❌ Poor accessibility |
| 59 | |
| 60 | #### Option 3: Custom Cairo Rendering (Chosen) ✅ |
| 61 | **Pros:** |
| 62 | - ✅ **Complete control** over appearance (exact vision match) |
| 63 | - ✅ Seamless floating text aesthetic |
| 64 | - ✅ Smooth hover effects (darker gray) |
| 65 | - ✅ Color coding exactly as specified |
| 66 | - ✅ Back/forward awareness (dim previously-visited segments) |
| 67 | - ✅ No button borders or padding artifacts |
| 68 | |
| 69 | **Cons:** |
| 70 | - ⚠️ More code (~350 lines) |
| 71 | - ⚠️ Manual hit-testing required |
| 72 | - ⚠️ 2-3 days implementation time |
| 73 | |
| 74 | --- |
| 75 | |
| 76 | ## WHY CAIRO? |
| 77 | |
| 78 | **Decision rationale:** |
| 79 | 1. **Achieves the vision exactly** - No compromises on appearance |
| 80 | 2. **Performance is negligible** - Breadcrumb is tiny compared to treemap (5-10 text segments vs 100s-1000s of rectangles) |
| 81 | 3. **State safety is already solved** - GTK event loop serialization + cached path segments |
| 82 | 4. **You're already using Cairo** - Treemap widget proves you know the APIs |
| 83 | 5. **Seamless look** - Floating text with precise hover zones, no visual artifacts |
| 84 | |
| 85 | ### Performance Considerations: |
| 86 | |
| 87 | **Impact:** <0.1% CPU overhead |
| 88 | - Breadcrumb renders 5-10 text segments (trivial) |
| 89 | - Treemap renders 100s-1000s of rectangles (heavy) - breadcrumb is nothing by comparison |
| 90 | - Pango text rendering is GPU-accelerated |
| 91 | - Optimization: Only redraw on hover **state change**, not every mouse move |
| 92 | |
| 93 | **During scans:** |
| 94 | - Zero overhead - breadcrumb only updates on navigation events |
| 95 | - Same thread-safety as current implementation (GTK main loop serialization) |
| 96 | - Cache path segments on main thread, draw function reads cached data |
| 97 | |
| 98 | ### State Corruption Risk: |
| 99 | |
| 100 | **Assessment:** Same as current implementation (safe with caching) |
| 101 | - Current `breadcrumb_callback()` already accesses `current_view` pointer |
| 102 | - Protection via GTK event loop (all widget access on main thread) |
| 103 | - Progressive scanner uses `g_idle_add()` for thread-safe callbacks |
| 104 | - **Strategy:** Cache path segments when `breadcrumb_callback()` fires, draw function reads cache only |
| 105 | |
| 106 | --- |
| 107 | |
| 108 | ## IMPLEMENTATION PLAN |
| 109 | |
| 110 | ### Phase 1: Create Custom Breadcrumb Widget Module |
| 111 | |
| 112 | **File:** `src/gui/breadcrumb_widget.f90` |
| 113 | |
| 114 | **Tasks:** |
| 115 | 1. Create module skeleton with Cairo drawing area |
| 116 | 2. Define cached state variables: |
| 117 | ```fortran |
| 118 | ! Path segment cache (updated on main thread only) |
| 119 | character(len=256), dimension(50), save :: cached_segments |
| 120 | integer, save :: cached_segment_count = 0 |
| 121 | |
| 122 | ! Hover state |
| 123 | integer, save :: hovered_segment = 0 ! 0 = none, 1+ = segment index |
| 124 | integer, save :: last_hovered_segment = -1 |
| 125 | |
| 126 | ! Click bounds for each segment |
| 127 | type :: segment_bounds |
| 128 | integer :: x, y, width, height |
| 129 | end type |
| 130 | type(segment_bounds), dimension(50), save :: segment_rects |
| 131 | ``` |
| 132 | |
| 133 | 3. Create widget initialization function: |
| 134 | ```fortran |
| 135 | function create_breadcrumb_widget() result(widget) |
| 136 | type(c_ptr) :: widget |
| 137 | ! Create GtkDrawingArea |
| 138 | ! Set draw function |
| 139 | ! Attach motion/click controllers |
| 140 | end function |
| 141 | ``` |
| 142 | |
| 143 | **Estimated time:** 2-3 hours |
| 144 | |
| 145 | --- |
| 146 | |
| 147 | ### Phase 2: Path Parsing Logic |
| 148 | |
| 149 | **Tasks:** |
| 150 | 1. **Split path into segments:** |
| 151 | ```fortran |
| 152 | ! Input: "/Users/matt/Documents/sniffly" |
| 153 | ! Output: |
| 154 | ! segments(1) = "/" |
| 155 | ! segments(2) = "/Users" |
| 156 | ! segments(3) = "/Users/matt" |
| 157 | ! segments(4) = "/Users/matt/Documents" |
| 158 | ! segments(5) = "/Users/matt/Documents/sniffly" |
| 159 | ! (Full paths for click navigation, but display only last component) |
| 160 | ``` |
| 161 | |
| 162 | 2. **Handle ~ abbreviation:** |
| 163 | ```fortran |
| 164 | ! Replace /Users/matt with ~ for display |
| 165 | ! But keep full path for navigation |
| 166 | ``` |
| 167 | |
| 168 | 3. **Extract display names:** |
| 169 | ```fortran |
| 170 | ! From "/Users/matt", display "matt" |
| 171 | ! From "~/Documents", display "Documents" |
| 172 | ! From "/", display "/" |
| 173 | ``` |
| 174 | |
| 175 | 4. **Create `update_breadcrumb_cache()` function:** |
| 176 | ```fortran |
| 177 | subroutine update_breadcrumb_cache(full_path) |
| 178 | character(len=*), intent(in) :: full_path |
| 179 | ! Parse path into cached_segments |
| 180 | ! Store full paths for navigation |
| 181 | ! Trigger redraw |
| 182 | end subroutine |
| 183 | ``` |
| 184 | |
| 185 | **Estimated time:** 3-4 hours |
| 186 | |
| 187 | --- |
| 188 | |
| 189 | ### Phase 3: Cairo Draw Function |
| 190 | |
| 191 | **Tasks:** |
| 192 | 1. **Render each segment with Pango:** |
| 193 | ```fortran |
| 194 | subroutine draw_breadcrumb(area, cr, width, height, user_data) bind(c) |
| 195 | ! For each segment: |
| 196 | ! 1. Determine color based on state: |
| 197 | ! - Root (i==1): green |
| 198 | ! - Active (i==count): bold red |
| 199 | ! - Inactive: gray |
| 200 | ! - Hovered inactive: darker gray |
| 201 | ! 2. Set Cairo color |
| 202 | ! 3. Draw text with Pango |
| 203 | ! 4. Store bounds in segment_rects(i) |
| 204 | ! 5. Draw separator " / " |
| 205 | ! 6. Update x_offset |
| 206 | end subroutine |
| 207 | ``` |
| 208 | |
| 209 | 2. **Color logic:** |
| 210 | ```fortran |
| 211 | ! Root segment (/) |
| 212 | if (i == 1) then |
| 213 | call cairo_set_source_rgb(cr, 0.0_c_double, 0.6_c_double, 0.0_c_double) ! Green |
| 214 | |
| 215 | ! Active segment (last in path) |
| 216 | else if (i == cached_segment_count) then |
| 217 | call cairo_set_source_rgb(cr, 0.8_c_double, 0.0_c_double, 0.0_c_double) ! Bold red |
| 218 | font_desc = pango_font_description_from_string("Sans Bold 11"//c_null_char) |
| 219 | |
| 220 | ! Inactive segment (hovered) |
| 221 | else if (i == hovered_segment) then |
| 222 | call cairo_set_source_rgb(cr, 0.3_c_double, 0.3_c_double, 0.3_c_double) ! Darker gray |
| 223 | |
| 224 | ! Inactive segment (not hovered) |
| 225 | else |
| 226 | call cairo_set_source_rgb(cr, 0.5_c_double, 0.5_c_double, 0.5_c_double) ! Gray |
| 227 | end if |
| 228 | ``` |
| 229 | |
| 230 | 3. **Back/forward awareness integration:** |
| 231 | ```fortran |
| 232 | ! Check if segment is in backwards history |
| 233 | logical function is_backwards_segment(segment_path) |
| 234 | ! Compare with nav_history and nav_history_pos |
| 235 | ! If segment_path appears before current position, return true |
| 236 | end function |
| 237 | |
| 238 | ! Apply extra dimming to backwards segments |
| 239 | if (is_backwards_segment(cached_segments(i))) then |
| 240 | call cairo_set_source_rgba(cr, 0.5_c_double, 0.5_c_double, 0.5_c_double, 0.6_c_double) ! Gray + transparency |
| 241 | end if |
| 242 | ``` |
| 243 | |
| 244 | **Estimated time:** 5-6 hours |
| 245 | |
| 246 | --- |
| 247 | |
| 248 | ### Phase 4: Mouse Interaction |
| 249 | |
| 250 | **Tasks:** |
| 251 | 1. **Hit-testing function:** |
| 252 | ```fortran |
| 253 | function find_segment_at_position(x, y) result(segment_index) |
| 254 | real(c_double), intent(in) :: x, y |
| 255 | integer :: segment_index |
| 256 | integer :: i |
| 257 | |
| 258 | segment_index = 0 |
| 259 | do i = 1, cached_segment_count |
| 260 | if (x >= segment_rects(i)%x .and. & |
| 261 | x <= segment_rects(i)%x + segment_rects(i)%width .and. & |
| 262 | y >= segment_rects(i)%y .and. & |
| 263 | y <= segment_rects(i)%y + segment_rects(i)%height) then |
| 264 | segment_index = i |
| 265 | return |
| 266 | end if |
| 267 | end do |
| 268 | end function |
| 269 | ``` |
| 270 | |
| 271 | 2. **Motion callback (hover):** |
| 272 | ```fortran |
| 273 | subroutine on_breadcrumb_motion(controller, x, y, user_data) bind(c) |
| 274 | integer :: new_hovered |
| 275 | |
| 276 | new_hovered = find_segment_at_position(x, y) |
| 277 | |
| 278 | ! Only redraw if hover state CHANGED |
| 279 | if (new_hovered /= last_hovered_segment) then |
| 280 | last_hovered_segment = new_hovered |
| 281 | hovered_segment = new_hovered |
| 282 | call gtk_widget_queue_draw(breadcrumb_widget_ptr) |
| 283 | end if |
| 284 | end subroutine |
| 285 | ``` |
| 286 | |
| 287 | 3. **Click callback (navigation):** |
| 288 | ```fortran |
| 289 | subroutine on_breadcrumb_click(gesture, n_press, x, y, user_data) bind(c) |
| 290 | integer :: clicked_segment, levels_up |
| 291 | |
| 292 | clicked_segment = find_segment_at_position(x, y) |
| 293 | if (clicked_segment > 0 .and. clicked_segment < cached_segment_count) then |
| 294 | ! Navigate to clicked segment |
| 295 | levels_up = cached_segment_count - clicked_segment |
| 296 | call navigate_up(levels_up) |
| 297 | |
| 298 | ! Trigger navigation callback to update history/UI |
| 299 | if (associated(nav_callback)) call nav_callback() |
| 300 | |
| 301 | ! Redraw |
| 302 | call gtk_widget_queue_draw(breadcrumb_widget_ptr) |
| 303 | end if |
| 304 | end subroutine |
| 305 | ``` |
| 306 | |
| 307 | **Estimated time:** 3-4 hours |
| 308 | |
| 309 | --- |
| 310 | |
| 311 | ### Phase 5: Integration with gtk_app.f90 |
| 312 | |
| 313 | **Tasks:** |
| 314 | 1. **Replace current breadcrumb bar:** |
| 315 | - REMOVE lines 356-364 (old breadcrumb_bar creation) |
| 316 | - REMOVE `breadcrumb_box_ptr` global variable |
| 317 | - REMOVE `breadcrumb_buttons` array |
| 318 | - REMOVE `breadcrumb_count` tracking |
| 319 | |
| 320 | 2. **Add new breadcrumb widget:** |
| 321 | ```fortran |
| 322 | ! In on_activate(), after toolbar creation: |
| 323 | use breadcrumb_widget, only: create_breadcrumb_widget, update_breadcrumb_cache |
| 324 | |
| 325 | type(c_ptr) :: breadcrumb_area |
| 326 | |
| 327 | ! Create custom breadcrumb widget |
| 328 | breadcrumb_area = create_breadcrumb_widget() |
| 329 | call gtk_widget_set_size_request(breadcrumb_area, -1_c_int, 30_c_int) ! Height: 30px |
| 330 | call gtk_box_append(main_box, breadcrumb_area) |
| 331 | ``` |
| 332 | |
| 333 | 3. **Update breadcrumb_callback():** |
| 334 | ```fortran |
| 335 | subroutine breadcrumb_callback() |
| 336 | use breadcrumb_widget, only: update_breadcrumb_cache |
| 337 | use treemap_renderer, only: get_current_view_node |
| 338 | |
| 339 | current_view => get_current_view_node() |
| 340 | if (associated(current_view) .and. allocated(current_view%path)) then |
| 341 | ! Update custom breadcrumb cache |
| 342 | call update_breadcrumb_cache(current_view%path) |
| 343 | end if |
| 344 | |
| 345 | ! Existing history/status logic... |
| 346 | end subroutine |
| 347 | ``` |
| 348 | |
| 349 | 4. **CRITICAL: Unhook old click behavior:** |
| 350 | - REMOVE `on_breadcrumb_clicked()` function (lines 1328-1356) |
| 351 | - REMOVE `sniffly_update_breadcrumbs()` function (lines 1222-1325) |
| 352 | - Navigation will now be handled by breadcrumb_widget's click callback |
| 353 | |
| 354 | 5. **Update meson.build:** |
| 355 | ```meson |
| 356 | sources += [ |
| 357 | 'src/gui/breadcrumb_widget.f90', |
| 358 | # ... existing sources |
| 359 | ] |
| 360 | ``` |
| 361 | |
| 362 | **Estimated time:** 2-3 hours |
| 363 | |
| 364 | --- |
| 365 | |
| 366 | ### Phase 6: Back/Forward Awareness (Advanced Feature) |
| 367 | |
| 368 | **Tasks:** |
| 369 | 1. **Expose navigation history to breadcrumb:** |
| 370 | ```fortran |
| 371 | ! In gtk_app.f90, add public getter: |
| 372 | public :: get_navigation_history, get_navigation_position |
| 373 | |
| 374 | function get_navigation_history() result(history) |
| 375 | character(len=512), dimension(:), allocatable :: history |
| 376 | allocate(history(nav_history_count)) |
| 377 | history = nav_history(1:nav_history_count) |
| 378 | end function |
| 379 | ``` |
| 380 | |
| 381 | 2. **Check in breadcrumb draw function:** |
| 382 | ```fortran |
| 383 | ! In breadcrumb_widget.f90 draw function: |
| 384 | use gtk_app, only: get_navigation_history, get_navigation_position |
| 385 | |
| 386 | character(len=512), dimension(:), allocatable :: history |
| 387 | integer :: history_pos, j |
| 388 | logical :: is_backwards |
| 389 | |
| 390 | history = get_navigation_history() |
| 391 | history_pos = get_navigation_position() |
| 392 | |
| 393 | ! For each segment, check if it was in earlier history |
| 394 | is_backwards = .false. |
| 395 | do j = 1, history_pos - 1 ! Check all history before current |
| 396 | if (index(trim(history(j)), trim(cached_segments(i))) > 0) then |
| 397 | is_backwards = .true. |
| 398 | exit |
| 399 | end if |
| 400 | end do |
| 401 | |
| 402 | ! Apply dimming if backwards |
| 403 | if (is_backwards) then |
| 404 | alpha = 0.6_c_double ! More transparent |
| 405 | end if |
| 406 | ``` |
| 407 | |
| 408 | **Estimated time:** 2-3 hours |
| 409 | |
| 410 | --- |
| 411 | |
| 412 | ### Phase 7: Testing & Polish |
| 413 | |
| 414 | **Tasks:** |
| 415 | 1. **Test scenarios:** |
| 416 | - [ ] Navigate into nested directories - breadcrumb updates correctly |
| 417 | - [ ] Click middle segment - navigates up correct number of levels |
| 418 | - [ ] Hover over segments - color changes smoothly |
| 419 | - [ ] Click active (red) segment - no navigation (already there) |
| 420 | - [ ] Back/Forward navigation - backwards segments dim appropriately |
| 421 | - [ ] Rapid mouse movement - no excessive redraws |
| 422 | - [ ] Long paths - breadcrumb doesn't overflow window width |
| 423 | |
| 424 | 2. **Handle edge cases:** |
| 425 | - [ ] Root path `/` - shows only one segment |
| 426 | - [ ] Home path `~` - abbreviates correctly |
| 427 | - [ ] Very long paths - add ellipsis or scrolling? |
| 428 | - [ ] Window resize - breadcrumb reflows |
| 429 | |
| 430 | 3. **Performance validation:** |
| 431 | - [ ] Run profiler during hover - verify <0.1% CPU |
| 432 | - [ ] Check redraw frequency - only on state changes |
| 433 | - [ ] Test during active scans - no interference |
| 434 | |
| 435 | 4. **Visual polish:** |
| 436 | - [ ] Adjust colors to match GTK theme |
| 437 | - [ ] Fine-tune separator spacing |
| 438 | - [ ] Ensure alignment with toolbar |
| 439 | |
| 440 | **Estimated time:** 3-4 hours |
| 441 | |
| 442 | --- |
| 443 | |
| 444 | ## TOTAL EFFORT ESTIMATE |
| 445 | |
| 446 | - **Phase 1:** 2-3 hours (module setup) |
| 447 | - **Phase 2:** 3-4 hours (path parsing) |
| 448 | - **Phase 3:** 5-6 hours (Cairo drawing) |
| 449 | - **Phase 4:** 3-4 hours (mouse interaction) |
| 450 | - **Phase 5:** 2-3 hours (integration) |
| 451 | - **Phase 6:** 2-3 hours (back/forward awareness) |
| 452 | - **Phase 7:** 3-4 hours (testing & polish) |
| 453 | |
| 454 | **Total:** 20-27 hours (~2-3 days of focused work) |
| 455 | |
| 456 | --- |
| 457 | |
| 458 | ## CRITICAL NOTES |
| 459 | |
| 460 | ### Don't Forget: |
| 461 | 1. **Unhook old breadcrumb click behavior** - Remove `on_breadcrumb_clicked()` and related button logic |
| 462 | 2. **Remove old breadcrumb widgets** - Clean up `breadcrumb_box_ptr`, `breadcrumb_buttons`, etc. |
| 463 | 3. **Navigation callback integration** - Breadcrumb widget needs access to `navigate_up()` from treemap_renderer |
| 464 | 4. **Thread safety** - Always update cache on main thread via callbacks, never in draw function |
| 465 | 5. **Redraw optimization** - Only queue redraw when hover STATE changes, not on every mouse move |
| 466 | |
| 467 | ### Performance Gotchas: |
| 468 | - Cache path segments when navigation happens, not on every draw |
| 469 | - Use `last_hovered_segment` to detect state changes |
| 470 | - Pango layout creation can be expensive - consider caching layouts between redraws if profiling shows issues |
| 471 | |
| 472 | ### Future Enhancements: |
| 473 | - Tooltip on hover showing full path + size stats |
| 474 | - Keyboard navigation (Tab through segments, Enter to navigate) |
| 475 | - Context menu on right-click (same as treemap) |
| 476 | - Animated transitions when segments update |
| 477 | |
| 478 | --- |
| 479 | |
| 480 | ## SUCCESS CRITERIA |
| 481 | |
| 482 | ✅ **Vision achieved when:** |
| 483 | - [x] Breadcrumb shows path as seamless floating text (no button borders) |
| 484 | - [x] Root segment is green |
| 485 | - [x] Active (last) segment is bold red |
| 486 | - [x] Inactive segments are gray |
| 487 | - [x] Hovered segments darken |
| 488 | - [x] Clicking any segment navigates to that level |
| 489 | - [x] Back/forward navigation dims previously-visited segments |
| 490 | - [x] Performance <0.1% CPU overhead |
| 491 | - [x] No state corruption during scans |
| 492 | - [x] Looks visually seamless and polished |