| 1 | module tab_bar_mod |
| 2 | use renderer_mod |
| 3 | use tab_manager_mod |
| 4 | implicit none |
| 5 | private |
| 6 | |
| 7 | public :: tab_bar_render |
| 8 | public :: tab_bar_get_close_button_bounds |
| 9 | public :: CLOSE_BTN_SIZE, CLOSE_BTN_MARGIN |
| 10 | |
| 11 | ! Tab bar styling constants |
| 12 | real, parameter :: TAB_PADDING = 8.0 ! Horizontal padding inside tab |
| 13 | real, parameter :: TAB_MIN_WIDTH = 80.0 ! Minimum tab width |
| 14 | real, parameter :: TAB_MAX_WIDTH = 200.0 ! Maximum tab width |
| 15 | real, parameter :: CLOSE_BTN_SIZE = 14.0 ! Close button size (square) |
| 16 | real, parameter :: CLOSE_BTN_MARGIN = 6.0 ! Margin from tab right edge |
| 17 | |
| 18 | contains |
| 19 | |
| 20 | ! Get close button bounds for a specific tab (for hit testing) |
| 21 | subroutine tab_bar_get_close_button_bounds(tab_index, tab_count, win_width, bar_height, & |
| 22 | btn_x, btn_y, btn_size) |
| 23 | integer, intent(in) :: tab_index, tab_count, win_width, bar_height |
| 24 | real, intent(out) :: btn_x, btn_y, btn_size |
| 25 | real :: tab_width, tab_start_x |
| 26 | |
| 27 | tab_width = real(win_width) / real(tab_count) |
| 28 | if (tab_width > TAB_MAX_WIDTH) tab_width = TAB_MAX_WIDTH |
| 29 | if (tab_width < TAB_MIN_WIDTH) tab_width = TAB_MIN_WIDTH |
| 30 | |
| 31 | tab_start_x = real(tab_index - 1) * tab_width |
| 32 | btn_size = CLOSE_BTN_SIZE |
| 33 | btn_x = tab_start_x + tab_width - CLOSE_BTN_MARGIN - CLOSE_BTN_SIZE |
| 34 | btn_y = (real(bar_height) - CLOSE_BTN_SIZE) / 2.0 |
| 35 | end subroutine tab_bar_get_close_button_bounds |
| 36 | |
| 37 | ! Render the tab bar at the top of the window |
| 38 | subroutine tab_bar_render(ren, mgr, win_width, bar_height, cell_width, ascender, & |
| 39 | hover_tab, hover_close_btn) |
| 40 | type(renderer_t), intent(inout) :: ren |
| 41 | type(tab_manager_t), intent(in) :: mgr |
| 42 | integer, intent(in) :: win_width, bar_height, cell_width, ascender |
| 43 | integer, intent(in) :: hover_tab ! Which tab mouse is over (0 = none) |
| 44 | logical, intent(in) :: hover_close_btn ! Is mouse over close button? |
| 45 | integer :: i, title_len |
| 46 | real :: x, y, tab_width |
| 47 | real :: bg_r, bg_g, bg_b |
| 48 | real :: active_bg_r, active_bg_g, active_bg_b |
| 49 | real :: inactive_bg_r, inactive_bg_g, inactive_bg_b |
| 50 | real :: text_r, text_g, text_b |
| 51 | real :: divider_r, divider_g, divider_b |
| 52 | real :: close_btn_x, close_btn_y |
| 53 | real :: close_r, close_g, close_b |
| 54 | character(len=256) :: display_title |
| 55 | integer :: max_chars |
| 56 | |
| 57 | if (mgr%count <= 1) return |
| 58 | |
| 59 | ! Colors (slightly darker than terminal background for contrast) |
| 60 | ! Tab bar background |
| 61 | bg_r = 0.08 |
| 62 | bg_g = 0.08 |
| 63 | bg_b = 0.10 |
| 64 | |
| 65 | ! Active tab (lighter) |
| 66 | active_bg_r = 0.15 |
| 67 | active_bg_g = 0.15 |
| 68 | active_bg_b = 0.18 |
| 69 | |
| 70 | ! Inactive tab (darker) |
| 71 | inactive_bg_r = 0.10 |
| 72 | inactive_bg_g = 0.10 |
| 73 | inactive_bg_b = 0.12 |
| 74 | |
| 75 | ! Text color |
| 76 | text_r = 0.85 |
| 77 | text_g = 0.85 |
| 78 | text_b = 0.85 |
| 79 | |
| 80 | ! Divider color |
| 81 | divider_r = 0.25 |
| 82 | divider_g = 0.25 |
| 83 | divider_b = 0.28 |
| 84 | |
| 85 | ! Draw tab bar background |
| 86 | call renderer_draw_rect(ren, 0.0, 0.0, real(win_width), real(bar_height), & |
| 87 | bg_r, bg_g, bg_b, 1.0) |
| 88 | |
| 89 | ! Calculate tab width (evenly distributed, with min/max constraints) |
| 90 | if (mgr%count > 0) then |
| 91 | tab_width = real(win_width) / real(mgr%count) |
| 92 | if (tab_width > TAB_MAX_WIDTH) tab_width = TAB_MAX_WIDTH |
| 93 | if (tab_width < TAB_MIN_WIDTH) tab_width = TAB_MIN_WIDTH |
| 94 | else |
| 95 | tab_width = TAB_MIN_WIDTH |
| 96 | end if |
| 97 | |
| 98 | ! Maximum characters that fit in a tab (account for close button) |
| 99 | max_chars = int((tab_width - 2.0 * TAB_PADDING - CLOSE_BTN_SIZE - CLOSE_BTN_MARGIN) / real(cell_width)) |
| 100 | if (max_chars < 3) max_chars = 3 |
| 101 | |
| 102 | x = 0.0 |
| 103 | y = 0.0 |
| 104 | |
| 105 | ! Draw each tab |
| 106 | do i = 1, mgr%count |
| 107 | ! Tab background |
| 108 | if (i == mgr%active_index) then |
| 109 | call renderer_draw_rect(ren, x + 1.0, y, tab_width - 2.0, real(bar_height), & |
| 110 | active_bg_r, active_bg_g, active_bg_b, 1.0) |
| 111 | else |
| 112 | call renderer_draw_rect(ren, x + 1.0, y, tab_width - 2.0, real(bar_height), & |
| 113 | inactive_bg_r, inactive_bg_g, inactive_bg_b, 1.0) |
| 114 | end if |
| 115 | |
| 116 | ! Prepare display title (truncate if needed) |
| 117 | title_len = len_trim(mgr%tabs(i)%title) |
| 118 | if (title_len > max_chars) then |
| 119 | display_title = mgr%tabs(i)%title(1:max_chars-2) // '..' |
| 120 | title_len = max_chars |
| 121 | else |
| 122 | display_title = mgr%tabs(i)%title |
| 123 | end if |
| 124 | |
| 125 | ! Draw tab title |
| 126 | ! Center text vertically: y + (bar_height - cell_height) / 2 + ascender |
| 127 | ! But we use ascender as baseline offset |
| 128 | call renderer_draw_string(ren, x + TAB_PADDING, & |
| 129 | real(bar_height) / 2.0 + real(ascender) / 2.0, & |
| 130 | trim(display_title), text_r, text_g, text_b, 1.0) |
| 131 | |
| 132 | ! Draw close button (X) |
| 133 | close_btn_x = x + tab_width - CLOSE_BTN_MARGIN - CLOSE_BTN_SIZE |
| 134 | close_btn_y = (real(bar_height) - CLOSE_BTN_SIZE) / 2.0 |
| 135 | |
| 136 | ! Close button color: red on hover, gray otherwise |
| 137 | if (i == hover_tab .and. hover_close_btn) then |
| 138 | ! Hovered - show red background circle and white X |
| 139 | call renderer_draw_rect(ren, close_btn_x, close_btn_y, & |
| 140 | CLOSE_BTN_SIZE, CLOSE_BTN_SIZE, & |
| 141 | 0.8, 0.2, 0.2, 1.0) |
| 142 | close_r = 1.0 |
| 143 | close_g = 1.0 |
| 144 | close_b = 1.0 |
| 145 | else |
| 146 | ! Not hovered - subtle gray X |
| 147 | close_r = 0.5 |
| 148 | close_g = 0.5 |
| 149 | close_b = 0.5 |
| 150 | end if |
| 151 | |
| 152 | ! Draw X character centered in the button |
| 153 | call renderer_draw_string(ren, close_btn_x + 2.0, & |
| 154 | close_btn_y + real(ascender) - 2.0, & |
| 155 | 'x', close_r, close_g, close_b, 1.0) |
| 156 | |
| 157 | ! Draw vertical divider after tab (except last) |
| 158 | if (i < mgr%count) then |
| 159 | call renderer_draw_rect(ren, x + tab_width - 1.0, 4.0, 1.0, real(bar_height - 8), & |
| 160 | divider_r, divider_g, divider_b, 1.0) |
| 161 | end if |
| 162 | |
| 163 | x = x + tab_width |
| 164 | end do |
| 165 | |
| 166 | ! Draw bottom border line |
| 167 | call renderer_draw_rect(ren, 0.0, real(bar_height - 1), real(win_width), 1.0, & |
| 168 | divider_r, divider_g, divider_b, 1.0) |
| 169 | |
| 170 | end subroutine tab_bar_render |
| 171 | |
| 172 | end module tab_bar_mod |
| 173 |