start seamless mouse/kb input
- SHA
3bf0b4e5f7266ee9cd9f9eb4c64311df96c78cb2- Parents
-
a5a87a0 - Tree
ccad51c
3bf0b4e
3bf0b4e5f7266ee9cd9f9eb4c64311df96c78cb2a5a87a0
ccad51c| Status | File | + | - |
|---|---|---|---|
| M |
src/gui/gtk_app.f90
|
5 | 1 |
| M |
src/gui/treemap_widget.f90
|
33 | 25 |
| M |
src/rendering/treemap_renderer.f90
|
68 | 1 |
src/gui/gtk_app.f90modified@@ -13,7 +13,8 @@ module gtk_app | ||
| 13 | 13 | gtk_label_new, gtk_label_set_text, gtk_widget_set_halign, & |
| 14 | 14 | GTK_ALIGN_START |
| 15 | 15 | use g, only: g_application_run |
| 16 | - use treemap_widget, only: create_treemap_widget, set_scan_path, register_navigation_callback | |
| 16 | + use treemap_widget, only: create_treemap_widget, set_scan_path, register_navigation_callback, & | |
| 17 | + register_key_handler | |
| 17 | 18 | implicit none |
| 18 | 19 | private |
| 19 | 20 | |
@@ -174,6 +175,9 @@ contains | ||
| 174 | 175 | ! Add main box to window |
| 175 | 176 | call gtk_window_set_child(main_window_ptr, main_box) |
| 176 | 177 | |
| 178 | + ! Register keyboard handler on window (not widget) for global keyboard capture | |
| 179 | + call register_key_handler(main_window_ptr) | |
| 180 | + | |
| 177 | 181 | ! Show the window |
| 178 | 182 | call gtk_window_present(main_window_ptr) |
| 179 | 183 | |
src/gui/treemap_widget.f90modified@@ -11,7 +11,8 @@ module treemap_widget | ||
| 11 | 11 | implicit none |
| 12 | 12 | private |
| 13 | 13 | |
| 14 | - public :: create_treemap_widget, set_scan_path, get_widget_ptr, register_navigation_callback | |
| 14 | + public :: create_treemap_widget, set_scan_path, get_widget_ptr, register_navigation_callback, & | |
| 15 | + register_key_handler | |
| 15 | 16 | |
| 16 | 17 | ! Callback interface for navigation events |
| 17 | 18 | abstract interface |
@@ -87,14 +88,23 @@ contains | ||
| 87 | 88 | c_funloc(on_click), c_null_ptr) |
| 88 | 89 | call gtk_widget_add_controller(widget, click_controller) |
| 89 | 90 | |
| 90 | - ! Add keyboard event controller for navigation | |
| 91 | + print *, "Treemap widget created successfully" | |
| 92 | + end function create_treemap_widget | |
| 93 | + | |
| 94 | + ! Register keyboard handler (called from gtk_app after window is created) | |
| 95 | + subroutine register_key_handler(window) | |
| 96 | + use gtk, only: gtk_event_controller_key_new, g_signal_connect, gtk_widget_add_controller | |
| 97 | + type(c_ptr), value :: window | |
| 98 | + type(c_ptr) :: key_controller | |
| 99 | + | |
| 100 | + ! Add keyboard event controller to window for navigation | |
| 91 | 101 | key_controller = gtk_event_controller_key_new() |
| 92 | 102 | call g_signal_connect(key_controller, "key-pressed"//c_null_char, & |
| 93 | 103 | c_funloc(on_key_press), c_null_ptr) |
| 94 | - call gtk_widget_add_controller(widget, key_controller) | |
| 104 | + call gtk_widget_add_controller(window, key_controller) | |
| 95 | 105 | |
| 96 | - print *, "Treemap widget created successfully" | |
| 97 | - end function create_treemap_widget | |
| 106 | + print *, "Key handler registered on window" | |
| 107 | + end subroutine register_key_handler | |
| 98 | 108 | |
| 99 | 109 | ! Get widget pointer (for triggering redraws) |
| 100 | 110 | function get_widget_ptr() result(ptr) |
@@ -196,7 +206,8 @@ contains | ||
| 196 | 206 | |
| 197 | 207 | ! Keyboard callback - handle all keyboard navigation |
| 198 | 208 | function on_key_press(controller, keyval, keycode, state, user_data) bind(c) result(handled) |
| 199 | - use treemap_renderer, only: navigate_up, navigate_into_node, get_node_count, get_node_center_by_index | |
| 209 | + use treemap_renderer, only: navigate_up, navigate_into_node, get_node_count, & | |
| 210 | + get_node_center_by_index, find_node_in_direction | |
| 200 | 211 | type(c_ptr), value :: controller, user_data |
| 201 | 212 | integer(c_int), value :: keyval, keycode, state |
| 202 | 213 | integer(c_int) :: handled |
@@ -205,7 +216,7 @@ contains | ||
| 205 | 216 | |
| 206 | 217 | handled = 0_c_int ! Default: not handled |
| 207 | 218 | |
| 208 | - ! Arrow keys: Navigate through nodes | |
| 219 | + ! Arrow keys: Navigate through nodes using spatial navigation | |
| 209 | 220 | if (keyval == GDK_KEY_Left .or. keyval == GDK_KEY_Right .or. & |
| 210 | 221 | keyval == GDK_KEY_Up .or. keyval == GDK_KEY_Down) then |
| 211 | 222 | |
@@ -215,26 +226,23 @@ contains | ||
| 215 | 226 | node_count = get_node_count() |
| 216 | 227 | |
| 217 | 228 | if (node_count > 0) then |
| 218 | - ! Initialize keyboard hover if needed | |
| 219 | - if (keyboard_hover_index == 0) then | |
| 220 | - keyboard_hover_index = 1 | |
| 221 | - else | |
| 222 | - ! Move keyboard hover based on arrow key | |
| 223 | - if (keyval == GDK_KEY_Right .or. keyval == GDK_KEY_Down) then | |
| 224 | - keyboard_hover_index = keyboard_hover_index + 1 | |
| 225 | - if (keyboard_hover_index > node_count) keyboard_hover_index = 1 ! Wrap around | |
| 226 | - else if (keyval == GDK_KEY_Left .or. keyval == GDK_KEY_Up) then | |
| 227 | - keyboard_hover_index = keyboard_hover_index - 1 | |
| 228 | - if (keyboard_hover_index < 1) keyboard_hover_index = node_count ! Wrap around | |
| 229 | - end if | |
| 229 | + ! Determine direction: 1=up, 2=down, 3=left, 4=right | |
| 230 | + if (keyval == GDK_KEY_Up) then | |
| 231 | + keyboard_hover_index = find_node_in_direction(mouse_x, mouse_y, 1) | |
| 232 | + else if (keyval == GDK_KEY_Down) then | |
| 233 | + keyboard_hover_index = find_node_in_direction(mouse_x, mouse_y, 2) | |
| 234 | + else if (keyval == GDK_KEY_Left) then | |
| 235 | + keyboard_hover_index = find_node_in_direction(mouse_x, mouse_y, 3) | |
| 236 | + else if (keyval == GDK_KEY_Right) then | |
| 237 | + keyboard_hover_index = find_node_in_direction(mouse_x, mouse_y, 4) | |
| 230 | 238 | end if |
| 231 | 239 | |
| 232 | - ! Update mouse position to match keyboard hover for seamless transition | |
| 233 | - call get_node_center_by_index(keyboard_hover_index, mouse_x, mouse_y, success) | |
| 234 | - if (success) then | |
| 235 | - print *, "Arrow key - keyboard hover index: ", keyboard_hover_index, " at (", mouse_x, ",", mouse_y, ")" | |
| 236 | - else | |
| 237 | - print *, "Arrow key - failed to get node center for index: ", keyboard_hover_index | |
| 240 | + ! Update mouse position to center of selected node for seamless transition | |
| 241 | + if (keyboard_hover_index > 0) then | |
| 242 | + call get_node_center_by_index(keyboard_hover_index, mouse_x, mouse_y, success) | |
| 243 | + if (success) then | |
| 244 | + print *, "Arrow key - moved to node ", keyboard_hover_index, " at (", mouse_x, ",", mouse_y, ")" | |
| 245 | + end if | |
| 238 | 246 | end if |
| 239 | 247 | end if |
| 240 | 248 | |
src/rendering/treemap_renderer.f90modified@@ -16,7 +16,7 @@ module treemap_renderer | ||
| 16 | 16 | public :: scan_and_render, init_renderer, get_root_node, scan_and_render_with_hover, & |
| 17 | 17 | scan_and_render_with_interaction, find_node_at_position, navigate_into_node, & |
| 18 | 18 | navigate_up, get_breadcrumb_path, get_path_depth, get_node_count, & |
| 19 | - get_node_center_by_index | |
| 19 | + get_node_center_by_index, find_node_in_direction | |
| 20 | 20 | |
| 21 | 21 | ! Global state |
| 22 | 22 | type(file_node), save, target :: root_node |
@@ -395,6 +395,73 @@ contains | ||
| 395 | 395 | success = .true. |
| 396 | 396 | end subroutine get_node_center_by_index |
| 397 | 397 | |
| 398 | + ! Find the best node in a given direction from current position | |
| 399 | + ! direction: 1=up, 2=down, 3=left, 4=right | |
| 400 | + function find_node_in_direction(from_x, from_y, direction) result(best_index) | |
| 401 | + use iso_fortran_env, only: real64 | |
| 402 | + real(c_double), intent(in) :: from_x, from_y | |
| 403 | + integer, intent(in) :: direction | |
| 404 | + integer :: best_index | |
| 405 | + integer :: i | |
| 406 | + real(real64) :: cx, cy, dx, dy, dist, score, best_score | |
| 407 | + real(real64) :: directional_component, perpendicular_component | |
| 408 | + | |
| 409 | + best_index = 0 | |
| 410 | + best_score = 1.0d20 ! Large number | |
| 411 | + | |
| 412 | + if (.not. associated(current_view_node)) return | |
| 413 | + if (current_view_node%num_children == 0) return | |
| 414 | + | |
| 415 | + ! For each child node, calculate score based on direction | |
| 416 | + do i = 1, current_view_node%num_children | |
| 417 | + ! Get center of this node | |
| 418 | + cx = real(current_view_node%children(i)%bounds%x, real64) + & | |
| 419 | + real(current_view_node%children(i)%bounds%width, real64) / 2.0d0 | |
| 420 | + cy = real(current_view_node%children(i)%bounds%y, real64) + & | |
| 421 | + real(current_view_node%children(i)%bounds%height, real64) / 2.0d0 | |
| 422 | + | |
| 423 | + dx = cx - real(from_x, real64) | |
| 424 | + dy = cy - real(from_y, real64) | |
| 425 | + | |
| 426 | + ! Check if node is in the correct direction | |
| 427 | + select case (direction) | |
| 428 | + case (1) ! Up | |
| 429 | + if (dy >= 0.0d0) cycle ! Skip nodes below or at same level | |
| 430 | + directional_component = abs(dy) ! Distance upward | |
| 431 | + perpendicular_component = abs(dx) ! Horizontal offset | |
| 432 | + case (2) ! Down | |
| 433 | + if (dy <= 0.0d0) cycle ! Skip nodes above or at same level | |
| 434 | + directional_component = abs(dy) ! Distance downward | |
| 435 | + perpendicular_component = abs(dx) ! Horizontal offset | |
| 436 | + case (3) ! Left | |
| 437 | + if (dx >= 0.0d0) cycle ! Skip nodes to right or at same position | |
| 438 | + directional_component = abs(dx) ! Distance leftward | |
| 439 | + perpendicular_component = abs(dy) ! Vertical offset | |
| 440 | + case (4) ! Right | |
| 441 | + if (dx <= 0.0d0) cycle ! Skip nodes to left or at same position | |
| 442 | + directional_component = abs(dx) ! Distance rightward | |
| 443 | + perpendicular_component = abs(dy) ! Vertical offset | |
| 444 | + case default | |
| 445 | + cycle | |
| 446 | + end select | |
| 447 | + | |
| 448 | + ! Score: prioritize alignment (low perpendicular) and closeness (low directional) | |
| 449 | + ! Weight perpendicular offset more heavily to prefer aligned nodes | |
| 450 | + score = directional_component + perpendicular_component * 2.0d0 | |
| 451 | + | |
| 452 | + if (score < best_score) then | |
| 453 | + best_score = score | |
| 454 | + best_index = i | |
| 455 | + end if | |
| 456 | + end do | |
| 457 | + | |
| 458 | + ! If no node found in direction, wrap to closest node in any direction | |
| 459 | + if (best_index == 0 .and. current_view_node%num_children > 0) then | |
| 460 | + best_index = 1 ! Default to first node | |
| 461 | + end if | |
| 462 | + | |
| 463 | + end function find_node_in_direction | |
| 464 | + | |
| 398 | 465 | ! Render hover highlight overlay |
| 399 | 466 | subroutine render_hover_highlight(cr, node) |
| 400 | 467 | type(c_ptr), intent(in) :: cr |