Fortran · 10634 bytes Raw Blame History
1 ! Render state module - holds state for the render callback
2 ! This allows do_render to be a module procedure instead of an internal
3 ! procedure, avoiding the need for trampolines and executable stack.
4 module render_state_mod
5 use window_mod
6 use gl_bindings
7 use renderer_mod
8 use terminal_mod
9 use screen_mod
10 use cell_mod
11 use config_mod
12 use cursor_mod, only: CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR
13 use tab_manager_mod
14 use tab_bar_mod
15 use pane_mod
16 use selection_mod, only: selection_t, selection_contains
17 implicit none
18 private
19
20 public :: render_state_init
21 public :: render_state_update_blink
22 public :: do_render
23
24 ! Pointers to main program state
25 type(window_t), pointer, save :: rs_win => null()
26 type(renderer_t), pointer, save :: rs_ren => null()
27 type(tab_manager_t), pointer, save :: rs_tab_mgr => null()
28 type(config_t), pointer, save :: rs_cfg => null()
29
30 ! Scalar state (copied, not pointed)
31 integer, save :: rs_prev_width = 0
32 integer, save :: rs_prev_height = 0
33 integer, save :: rs_win_width = 0
34 integer, save :: rs_win_height = 0
35 integer, save :: rs_cell_width = 10
36 integer, save :: rs_cell_height = 18
37 integer, save :: rs_ascender = 14
38 logical, save :: rs_cursor_blink_visible = .true.
39
40 ! Scrollback line buffer
41 type(cell_t), allocatable, save :: rs_sb_line(:)
42
43 contains
44
45 ! Initialize render state with pointers to main program data
46 subroutine render_state_init(win, ren, tab_mgr, cfg, &
47 cell_width, cell_height, ascender, &
48 win_width, win_height)
49 type(window_t), target, intent(in) :: win
50 type(renderer_t), target, intent(in) :: ren
51 type(tab_manager_t), target, intent(in) :: tab_mgr
52 type(config_t), target, intent(in) :: cfg
53 integer, intent(in) :: cell_width, cell_height, ascender
54 integer, intent(in) :: win_width, win_height
55
56 rs_win => win
57 rs_ren => ren
58 rs_tab_mgr => tab_mgr
59 rs_cfg => cfg
60 rs_cell_width = cell_width
61 rs_cell_height = cell_height
62 rs_ascender = ascender
63 rs_win_width = win_width
64 rs_win_height = win_height
65 rs_prev_width = win_width
66 rs_prev_height = win_height
67 end subroutine render_state_init
68
69 ! Update render state with current values
70 subroutine render_state_update_blink(cursor_blink_visible)
71 logical, intent(in) :: cursor_blink_visible
72 rs_cursor_blink_visible = cursor_blink_visible
73 end subroutine render_state_update_blink
74
75 ! Main render subroutine - now a module procedure, not internal
76 subroutine do_render()
77 integer :: render_width, render_height
78 integer :: pane_idx, tab_idx, tab_bar_height
79 integer :: scissor_x, scissor_y, scissor_w, scissor_h
80 integer :: hover_tab
81 logical :: hover_close
82 real :: dim_factor, pane_x_offset, pane_y_offset
83 real :: bg_r, bg_g, bg_b
84 type(pane_t), pointer :: cur_pane
85 type(terminal_t), pointer :: pane_term
86 type(screen_t), pointer :: pane_scr
87 type(selection_t) :: sel
88 type(cell_t) :: cell
89 integer :: row, col, scroll_offset, sb_offset, screen_row
90 real :: x, y, r, g, b
91
92 ! Guard against uninitialized state
93 if (.not. associated(rs_win)) return
94 if (.not. associated(rs_ren)) return
95 if (.not. associated(rs_tab_mgr)) return
96 if (.not. associated(rs_cfg)) return
97
98 ! Get current window size and update projection if needed
99 call window_get_size(rs_win, render_width, render_height)
100 if (render_width /= rs_prev_width .or. render_height /= rs_prev_height) then
101 rs_prev_width = render_width
102 rs_prev_height = render_height
103 rs_win_width = render_width
104 rs_win_height = render_height
105 call renderer_set_projection(rs_ren, rs_win_width, rs_win_height)
106 end if
107
108 ! Clear screen with background color and opacity from config
109 bg_r = real(rs_cfg%bg_color%r) / 255.0
110 bg_g = real(rs_cfg%bg_color%g) / 255.0
111 bg_b = real(rs_cfg%bg_color%b) / 255.0
112 call glClearColor(bg_r, bg_g, bg_b, rs_cfg%window_opacity)
113 call glClear(GL_COLOR_BUFFER_BIT)
114
115 ! Render terminal buffer
116 call renderer_begin(rs_ren)
117
118 ! Get tab hover state for close button highlighting
119 call window_get_tab_hover(hover_tab, hover_close)
120
121 ! Render tab bar at top
122 call tab_bar_render(rs_ren, rs_tab_mgr, rs_win_width, rs_tab_mgr%bar_height, &
123 rs_cell_width, rs_ascender, hover_tab, hover_close)
124
125 ! Calculate effective tab bar height (hidden when only 1 tab)
126 if (rs_tab_mgr%count > 1) then
127 tab_bar_height = rs_tab_mgr%bar_height
128 else
129 tab_bar_height = 0
130 end if
131
132 ! Update window module with tab bar info for click detection
133 call window_set_tab_bar_info(tab_bar_height, rs_tab_mgr%count, rs_win_width)
134
135 ! Guard against no active tab
136 if (rs_tab_mgr%active_index < 1 .or. rs_tab_mgr%active_index > rs_tab_mgr%count) then
137 call renderer_flush(rs_ren)
138 call window_swap_buffers(rs_win)
139 return
140 end if
141
142 tab_idx = rs_tab_mgr%active_index
143
144 ! Get current selection for highlighting (only applies to active pane)
145 sel = window_get_selection()
146
147 ! Render each pane in the active tab
148 do pane_idx = 1, rs_tab_mgr%tabs(tab_idx)%pane_count
149 cur_pane => rs_tab_mgr%tabs(tab_idx)%panes(pane_idx)
150 pane_term => cur_pane%term
151 pane_scr => terminal_active_screen(pane_term)
152
153 ! Determine dimming factor (inactive panes are dimmed)
154 if (cur_pane%active) then
155 dim_factor = 1.0
156 else
157 dim_factor = 0.6
158 end if
159
160 ! Calculate pane offset for rendering
161 pane_x_offset = real(cur_pane%x)
162 pane_y_offset = real(cur_pane%y)
163
164 ! Enable scissor test for this pane's viewport
165 ! OpenGL uses bottom-left origin, so convert from top-left
166 scissor_x = cur_pane%x
167 scissor_y = rs_win_height - cur_pane%y - cur_pane%height
168 scissor_w = cur_pane%width
169 scissor_h = cur_pane%height
170 call glEnable(GL_SCISSOR_TEST)
171 call glScissor(scissor_x, scissor_y, scissor_w, scissor_h)
172
173 scroll_offset = terminal_get_scroll_offset(pane_term)
174
175 ! Allocate/resize scrollback line buffer if needed
176 if (.not. allocated(rs_sb_line)) then
177 allocate(rs_sb_line(cur_pane%cols))
178 else if (size(rs_sb_line) /= cur_pane%cols) then
179 deallocate(rs_sb_line)
180 allocate(rs_sb_line(cur_pane%cols))
181 end if
182
183 do row = 1, pane_scr%rows
184 ! Cell top-left y coordinate relative to pane
185 y = real(row - 1) * rs_cell_height + pane_y_offset
186
187 ! Determine if this row shows scrollback or screen content
188 sb_offset = scroll_offset - row + 1
189
190 if (sb_offset > 0 .and. sb_offset <= terminal_get_scrollback_count(pane_term)) then
191 ! This row shows scrollback content
192 call terminal_get_scrollback_line(pane_term, sb_offset - 1, rs_sb_line, cur_pane%cols)
193 do col = 1, min(cur_pane%cols, pane_scr%cols)
194 cell = rs_sb_line(col)
195
196 ! Skip continuation cells (2nd half of wide chars)
197 if (cell%is_continuation) cycle
198
199 x = real(col - 1) * rs_cell_width + pane_x_offset
200
201 ! Draw selection background if selected (only for active pane)
202 if (cur_pane%active .and. selection_contains(sel, row, col)) then
203 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
204 0.3, 0.3, 0.6, 1.0)
205 end if
206
207 ! Only render cells with actual text content
208 if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then
209 r = real(cell%fg%r) / 255.0 * dim_factor
210 g = real(cell%fg%g) / 255.0 * dim_factor
211 b = real(cell%fg%b) / 255.0 * dim_factor
212 call renderer_draw_char(rs_ren, x, y + real(rs_ascender), cell%codepoint, r, g, b, 1.0)
213 end if
214 end do
215 else
216 ! This row shows screen content
217 screen_row = row - scroll_offset
218 if (screen_row >= 1 .and. screen_row <= pane_scr%rows) then
219 do col = 1, pane_scr%cols
220 cell = screen_get_cell(pane_scr, screen_row, col)
221
222 ! Skip continuation cells (2nd half of wide chars)
223 if (cell%is_continuation) cycle
224
225 x = real(col - 1) * rs_cell_width + pane_x_offset
226
227 ! Draw selection background if selected (only for active pane)
228 if (cur_pane%active .and. selection_contains(sel, row, col)) then
229 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
230 0.3, 0.3, 0.6, 1.0)
231 end if
232
233 ! Only render cells with actual text content
234 if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then
235 r = real(cell%fg%r) / 255.0 * dim_factor
236 g = real(cell%fg%g) / 255.0 * dim_factor
237 b = real(cell%fg%b) / 255.0 * dim_factor
238 call renderer_draw_char(rs_ren, x, y + real(rs_ascender), cell%codepoint, r, g, b, 1.0)
239 end if
240 end do
241 end if
242 end if
243 end do
244
245 ! Draw cursor if visible (only for active pane and when not scrolled back)
246 if (cur_pane%active .and. pane_term%cursor%visible .and. scroll_offset == 0) then
247 ! Check blink state - only hide cursor if blink is enabled and in off phase
248 if (.not. pane_term%cursor%blink .or. rs_cursor_blink_visible) then
249 x = real(pane_term%cursor%col - 1) * rs_cell_width + pane_x_offset
250 y = real(pane_term%cursor%row - 1) * rs_cell_height + pane_y_offset
251
252 select case (pane_term%cursor%style)
253 case (CURSOR_BLOCK)
254 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
255 0.7, 0.7, 0.7, 0.8)
256 case (CURSOR_UNDERLINE)
257 call renderer_draw_rect(rs_ren, x, y + real(rs_ascender), &
258 real(rs_cell_width), 2.0, 0.7, 0.7, 0.7, 1.0)
259 case (CURSOR_BAR)
260 call renderer_draw_rect(rs_ren, x, y, 2.0, real(rs_cell_height), 0.7, 0.7, 0.7, 1.0)
261 case default
262 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
263 0.7, 0.7, 0.7, 0.8)
264 end select
265 end if
266 end if
267
268 call glDisable(GL_SCISSOR_TEST)
269 end do
270
271 call renderer_flush(rs_ren)
272
273 ! Swap buffers
274 call window_swap_buffers(rs_win)
275 end subroutine do_render
276
277 end module render_state_mod
278