Fortran · 10300 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 real :: dim_factor, pane_x_offset, pane_y_offset
81 real :: bg_r, bg_g, bg_b
82 type(pane_t), pointer :: cur_pane
83 type(terminal_t), pointer :: pane_term
84 type(screen_t), pointer :: pane_scr
85 type(selection_t) :: sel
86 type(cell_t) :: cell
87 integer :: row, col, scroll_offset, sb_offset, screen_row
88 real :: x, y, r, g, b
89
90 ! Guard against uninitialized state
91 if (.not. associated(rs_win)) return
92 if (.not. associated(rs_ren)) return
93 if (.not. associated(rs_tab_mgr)) return
94 if (.not. associated(rs_cfg)) return
95
96 ! Get current window size and update projection if needed
97 call window_get_size(rs_win, render_width, render_height)
98 if (render_width /= rs_prev_width .or. render_height /= rs_prev_height) then
99 rs_prev_width = render_width
100 rs_prev_height = render_height
101 rs_win_width = render_width
102 rs_win_height = render_height
103 call renderer_set_projection(rs_ren, rs_win_width, rs_win_height)
104 end if
105
106 ! Clear screen with background color and opacity from config
107 bg_r = real(rs_cfg%bg_color%r) / 255.0
108 bg_g = real(rs_cfg%bg_color%g) / 255.0
109 bg_b = real(rs_cfg%bg_color%b) / 255.0
110 call glClearColor(bg_r, bg_g, bg_b, rs_cfg%window_opacity)
111 call glClear(GL_COLOR_BUFFER_BIT)
112
113 ! Render terminal buffer
114 call renderer_begin(rs_ren)
115
116 ! Render tab bar at top
117 call tab_bar_render(rs_ren, rs_tab_mgr, rs_win_width, rs_tab_mgr%bar_height, &
118 rs_cell_width, rs_ascender)
119
120 ! Calculate effective tab bar height (hidden when only 1 tab)
121 if (rs_tab_mgr%count > 1) then
122 tab_bar_height = rs_tab_mgr%bar_height
123 else
124 tab_bar_height = 0
125 end if
126
127 ! Guard against no active tab
128 if (rs_tab_mgr%active_index < 1 .or. rs_tab_mgr%active_index > rs_tab_mgr%count) then
129 call renderer_flush(rs_ren)
130 call window_swap_buffers(rs_win)
131 return
132 end if
133
134 tab_idx = rs_tab_mgr%active_index
135
136 ! Get current selection for highlighting (only applies to active pane)
137 sel = window_get_selection()
138
139 ! Render each pane in the active tab
140 do pane_idx = 1, rs_tab_mgr%tabs(tab_idx)%pane_count
141 cur_pane => rs_tab_mgr%tabs(tab_idx)%panes(pane_idx)
142 pane_term => cur_pane%term
143 pane_scr => terminal_active_screen(pane_term)
144
145 ! Determine dimming factor (inactive panes are dimmed)
146 if (cur_pane%active) then
147 dim_factor = 1.0
148 else
149 dim_factor = 0.6
150 end if
151
152 ! Calculate pane offset for rendering
153 pane_x_offset = real(cur_pane%x)
154 pane_y_offset = real(cur_pane%y)
155
156 ! Enable scissor test for this pane's viewport
157 ! OpenGL uses bottom-left origin, so convert from top-left
158 scissor_x = cur_pane%x
159 scissor_y = rs_win_height - cur_pane%y - cur_pane%height
160 scissor_w = cur_pane%width
161 scissor_h = cur_pane%height
162 call glEnable(GL_SCISSOR_TEST)
163 call glScissor(scissor_x, scissor_y, scissor_w, scissor_h)
164
165 scroll_offset = terminal_get_scroll_offset(pane_term)
166
167 ! Allocate/resize scrollback line buffer if needed
168 if (.not. allocated(rs_sb_line)) then
169 allocate(rs_sb_line(cur_pane%cols))
170 else if (size(rs_sb_line) /= cur_pane%cols) then
171 deallocate(rs_sb_line)
172 allocate(rs_sb_line(cur_pane%cols))
173 end if
174
175 do row = 1, pane_scr%rows
176 ! Cell top-left y coordinate relative to pane
177 y = real(row - 1) * rs_cell_height + pane_y_offset
178
179 ! Determine if this row shows scrollback or screen content
180 sb_offset = scroll_offset - row + 1
181
182 if (sb_offset > 0 .and. sb_offset <= terminal_get_scrollback_count(pane_term)) then
183 ! This row shows scrollback content
184 call terminal_get_scrollback_line(pane_term, sb_offset - 1, rs_sb_line, cur_pane%cols)
185 do col = 1, min(cur_pane%cols, pane_scr%cols)
186 cell = rs_sb_line(col)
187
188 ! Skip continuation cells (2nd half of wide chars)
189 if (cell%is_continuation) cycle
190
191 x = real(col - 1) * rs_cell_width + pane_x_offset
192
193 ! Draw selection background if selected (only for active pane)
194 if (cur_pane%active .and. selection_contains(sel, row, col)) then
195 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
196 0.3, 0.3, 0.6, 1.0)
197 end if
198
199 ! Only render cells with actual text content
200 if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then
201 r = real(cell%fg%r) / 255.0 * dim_factor
202 g = real(cell%fg%g) / 255.0 * dim_factor
203 b = real(cell%fg%b) / 255.0 * dim_factor
204 call renderer_draw_char(rs_ren, x, y + real(rs_ascender), cell%codepoint, r, g, b, 1.0)
205 end if
206 end do
207 else
208 ! This row shows screen content
209 screen_row = row - scroll_offset
210 if (screen_row >= 1 .and. screen_row <= pane_scr%rows) then
211 do col = 1, pane_scr%cols
212 cell = screen_get_cell(pane_scr, screen_row, col)
213
214 ! Skip continuation cells (2nd half of wide chars)
215 if (cell%is_continuation) cycle
216
217 x = real(col - 1) * rs_cell_width + pane_x_offset
218
219 ! Draw selection background if selected (only for active pane)
220 if (cur_pane%active .and. selection_contains(sel, row, col)) then
221 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
222 0.3, 0.3, 0.6, 1.0)
223 end if
224
225 ! Only render cells with actual text content
226 if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then
227 r = real(cell%fg%r) / 255.0 * dim_factor
228 g = real(cell%fg%g) / 255.0 * dim_factor
229 b = real(cell%fg%b) / 255.0 * dim_factor
230 call renderer_draw_char(rs_ren, x, y + real(rs_ascender), cell%codepoint, r, g, b, 1.0)
231 end if
232 end do
233 end if
234 end if
235 end do
236
237 ! Draw cursor if visible (only for active pane and when not scrolled back)
238 if (cur_pane%active .and. pane_term%cursor%visible .and. scroll_offset == 0) then
239 ! Check blink state - only hide cursor if blink is enabled and in off phase
240 if (.not. pane_term%cursor%blink .or. rs_cursor_blink_visible) then
241 x = real(pane_term%cursor%col - 1) * rs_cell_width + pane_x_offset
242 y = real(pane_term%cursor%row - 1) * rs_cell_height + pane_y_offset
243
244 select case (pane_term%cursor%style)
245 case (CURSOR_BLOCK)
246 call renderer_draw_rect(rs_ren, x, y, real(rs_cell_width), real(rs_cell_height), &
247 0.7, 0.7, 0.7, 0.8)
248 case (CURSOR_UNDERLINE)
249 call renderer_draw_rect(rs_ren, x, y + real(rs_ascender), &
250 real(rs_cell_width), 2.0, 0.7, 0.7, 0.7, 1.0)
251 case (CURSOR_BAR)
252 call renderer_draw_rect(rs_ren, x, y, 2.0, real(rs_cell_height), 0.7, 0.7, 0.7, 1.0)
253 case default
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 end select
257 end if
258 end if
259
260 call glDisable(GL_SCISSOR_TEST)
261 end do
262
263 call renderer_flush(rs_ren)
264
265 ! Swap buffers
266 call window_swap_buffers(rs_win)
267 end subroutine do_render
268
269 end module render_state_mod
270