@@ -1,6 +1,5 @@ |
| 1 | program fortty | 1 | program fortty |
| 2 | use window_mod | 2 | use window_mod |
| 3 | - use selection_mod, only: selection_contains | | |
| 4 | use gl_bindings | 3 | use gl_bindings |
| 5 | use renderer_mod | 4 | use renderer_mod |
| 6 | use font_mod, only: font_find_monospace, font_find_for_codepoint | 5 | use font_mod, only: font_find_monospace, font_find_for_codepoint |
@@ -8,14 +7,14 @@ program fortty |
| 8 | use terminal_mod | 7 | use terminal_mod |
| 9 | use parser_mod | 8 | use parser_mod |
| 10 | use screen_mod | 9 | use screen_mod |
| 11 | - use cell_mod, only: cell_t, set_palette_color, set_default_colors | 10 | + use cell_mod, only: set_palette_color, set_default_colors |
| 12 | use config_mod | 11 | use config_mod |
| 13 | - use cursor_mod, only: CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR | | |
| 14 | use glfw_bindings, only: glfwGetTime | 12 | use glfw_bindings, only: glfwGetTime |
| 15 | use tab_manager_mod | 13 | use tab_manager_mod |
| 16 | use tab_bar_mod | 14 | use tab_bar_mod |
| 17 | use pane_mod | 15 | use pane_mod |
| 18 | use layout_mod, only: DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN | 16 | use layout_mod, only: DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN |
| | 17 | + use render_state_mod, only: render_state_init, render_state_update_blink, do_render |
| 19 | implicit none | 18 | implicit none |
| 20 | | 19 | |
| 21 | type(window_t) :: win | 20 | type(window_t) :: win |
@@ -24,7 +23,6 @@ program fortty |
| 24 | type(pane_t), pointer :: active_pane | 23 | type(pane_t), pointer :: active_pane |
| 25 | type(terminal_t), pointer :: term | 24 | type(terminal_t), pointer :: term |
| 26 | type(pty_t), pointer :: active_pty | 25 | type(pty_t), pointer :: active_pty |
| 27 | - type(cell_t) :: cell | | |
| 28 | type(config_t) :: cfg | 26 | type(config_t) :: cfg |
| 29 | integer :: win_width, win_height | 27 | integer :: win_width, win_height |
| 30 | integer :: prev_width, prev_height | 28 | integer :: prev_width, prev_height |
@@ -33,15 +31,12 @@ program fortty |
| 33 | character(len=256) :: font_path, fallback_path | 31 | character(len=256) :: font_path, fallback_path |
| 34 | character(len=4096) :: pty_buffer | 32 | character(len=4096) :: pty_buffer |
| 35 | character(len=256) :: response_buf | 33 | character(len=256) :: response_buf |
| 36 | - integer :: nbytes, i, j, k, row, col, scroll_offset, sb_offset, screen_row | 34 | + integer :: nbytes, i, j, k |
| 37 | integer :: response_len, tab_action, pane_action, tab_bar_height | 35 | integer :: response_len, tab_action, pane_action, tab_bar_height |
| 38 | - real :: x, y, r, g, b, bg_r, bg_g, bg_b | | |
| 39 | - type(cell_t), allocatable :: sb_line(:) | | |
| 40 | integer :: cell_width, cell_height, ascender ! From font metrics | 36 | integer :: cell_width, cell_height, ascender ! From font metrics |
| 41 | real(8) :: current_time, last_time, blink_timer | 37 | real(8) :: current_time, last_time, blink_timer |
| 42 | logical :: cursor_blink_visible, any_pty_alive | 38 | logical :: cursor_blink_visible, any_pty_alive |
| 43 | logical :: was_focused, is_focused | 39 | logical :: was_focused, is_focused |
| 44 | - type(selection_t) :: sel | | |
| 45 | integer :: font_delta, new_font_size, base_font_size | 40 | integer :: font_delta, new_font_size, base_font_size |
| 46 | character(len=256) :: font_path_saved, fallback_path_saved | 41 | character(len=256) :: font_path_saved, fallback_path_saved |
| 47 | | 42 | |
@@ -229,6 +224,10 @@ program fortty |
| 229 | call window_set_pty(active_pty) | 224 | call window_set_pty(active_pty) |
| 230 | call window_set_terminal(term) | 225 | call window_set_terminal(term) |
| 231 | | 226 | |
| | 227 | + ! Initialize render state module with pointers to program state |
| | 228 | + call render_state_init(win, ren, tab_mgr, cfg, cell_width, cell_height, ascender, & |
| | 229 | + win_width, win_height) |
| | 230 | + |
| 232 | ! Register render callback for live resize support on macOS | 231 | ! Register render callback for live resize support on macOS |
| 233 | call window_set_render_callback(do_render) | 232 | call window_set_render_callback(do_render) |
| 234 | | 233 | |
@@ -251,6 +250,7 @@ program fortty |
| 251 | cursor_blink_visible = .not. cursor_blink_visible | 250 | cursor_blink_visible = .not. cursor_blink_visible |
| 252 | blink_timer = 0.0d0 | 251 | blink_timer = 0.0d0 |
| 253 | end if | 252 | end if |
| | 253 | + call render_state_update_blink(cursor_blink_visible) |
| 254 | | 254 | |
| 255 | ! Poll events first - this ensures resize callbacks fire BEFORE we render | 255 | ! Poll events first - this ensures resize callbacks fire BEFORE we render |
| 256 | ! so viewport and projection updates happen in the same frame | 256 | ! so viewport and projection updates happen in the same frame |
@@ -432,198 +432,12 @@ program fortty |
| 432 | end do | 432 | end do |
| 433 | | 433 | |
| 434 | ! Cleanup | 434 | ! Cleanup |
| 435 | - if (allocated(sb_line)) deallocate(sb_line) | | |
| 436 | call tab_manager_destroy(tab_mgr) | 435 | call tab_manager_destroy(tab_mgr) |
| 437 | call renderer_destroy(ren) | 436 | call renderer_destroy(ren) |
| 438 | call window_destroy(win) | 437 | call window_destroy(win) |
| 439 | | 438 | |
| 440 | contains | 439 | contains |
| 441 | | 440 | |
| 442 | - ! Internal render subroutine - can be called from main loop or resize callback | | |
| 443 | - ! Has access to all program variables through host association | | |
| 444 | - subroutine do_render() | | |
| 445 | - integer :: render_width, render_height | | |
| 446 | - integer :: pane_idx, tab_idx | | |
| 447 | - integer :: scissor_x, scissor_y, scissor_w, scissor_h | | |
| 448 | - real :: dim_factor, pane_x_offset, pane_y_offset | | |
| 449 | - type(pane_t), pointer :: cur_pane | | |
| 450 | - type(terminal_t), pointer :: pane_term | | |
| 451 | - type(screen_t), pointer :: pane_scr | | |
| 452 | - | | |
| 453 | - ! Get current window size and update projection if needed | | |
| 454 | - ! This is critical for live resize - we need to update projection | | |
| 455 | - ! to match the new viewport that was set by the framebuffer callback | | |
| 456 | - call window_get_size(win, render_width, render_height) | | |
| 457 | - if (render_width /= prev_width .or. render_height /= prev_height) then | | |
| 458 | - prev_width = render_width | | |
| 459 | - prev_height = render_height | | |
| 460 | - win_width = render_width | | |
| 461 | - win_height = render_height | | |
| 462 | - call renderer_set_projection(ren, win_width, win_height) | | |
| 463 | - end if | | |
| 464 | - | | |
| 465 | - ! Clear screen with background color and opacity from config | | |
| 466 | - bg_r = real(cfg%bg_color%r) / 255.0 | | |
| 467 | - bg_g = real(cfg%bg_color%g) / 255.0 | | |
| 468 | - bg_b = real(cfg%bg_color%b) / 255.0 | | |
| 469 | - call glClearColor(bg_r, bg_g, bg_b, cfg%window_opacity) | | |
| 470 | - call glClear(GL_COLOR_BUFFER_BIT) | | |
| 471 | - | | |
| 472 | - ! Render terminal buffer | | |
| 473 | - call renderer_begin(ren) | | |
| 474 | - | | |
| 475 | - ! Render tab bar at top | | |
| 476 | - call tab_bar_render(ren, tab_mgr, win_width, tab_mgr%bar_height, cell_width, ascender) | | |
| 477 | - | | |
| 478 | - ! Calculate effective tab bar height (hidden when only 1 tab) | | |
| 479 | - if (tab_mgr%count > 1) then | | |
| 480 | - tab_bar_height = tab_mgr%bar_height | | |
| 481 | - else | | |
| 482 | - tab_bar_height = 0 | | |
| 483 | - end if | | |
| 484 | - | | |
| 485 | - ! Guard against no active tab | | |
| 486 | - if (tab_mgr%active_index < 1 .or. tab_mgr%active_index > tab_mgr%count) then | | |
| 487 | - call renderer_flush(ren) | | |
| 488 | - call window_swap_buffers(win) | | |
| 489 | - return | | |
| 490 | - end if | | |
| 491 | - | | |
| 492 | - tab_idx = tab_mgr%active_index | | |
| 493 | - | | |
| 494 | - ! Get current selection for highlighting (only applies to active pane) | | |
| 495 | - sel = window_get_selection() | | |
| 496 | - | | |
| 497 | - ! Render each pane in the active tab | | |
| 498 | - do pane_idx = 1, tab_mgr%tabs(tab_idx)%pane_count | | |
| 499 | - cur_pane => tab_mgr%tabs(tab_idx)%panes(pane_idx) | | |
| 500 | - pane_term => cur_pane%term | | |
| 501 | - pane_scr => terminal_active_screen(pane_term) | | |
| 502 | - | | |
| 503 | - ! Determine dimming factor (inactive panes are dimmed) | | |
| 504 | - if (cur_pane%active) then | | |
| 505 | - dim_factor = 1.0 | | |
| 506 | - else | | |
| 507 | - dim_factor = 0.6 | | |
| 508 | - end if | | |
| 509 | - | | |
| 510 | - ! Calculate pane offset for rendering | | |
| 511 | - pane_x_offset = real(cur_pane%x) | | |
| 512 | - pane_y_offset = real(cur_pane%y) | | |
| 513 | - | | |
| 514 | - ! Enable scissor test for this pane's viewport | | |
| 515 | - ! OpenGL uses bottom-left origin, so convert from top-left | | |
| 516 | - scissor_x = cur_pane%x | | |
| 517 | - scissor_y = win_height - cur_pane%y - cur_pane%height | | |
| 518 | - scissor_w = cur_pane%width | | |
| 519 | - scissor_h = cur_pane%height | | |
| 520 | - call glEnable(GL_SCISSOR_TEST) | | |
| 521 | - call glScissor(scissor_x, scissor_y, scissor_w, scissor_h) | | |
| 522 | - | | |
| 523 | - scroll_offset = terminal_get_scroll_offset(pane_term) | | |
| 524 | - | | |
| 525 | - ! Allocate/resize scrollback line buffer if needed | | |
| 526 | - if (.not. allocated(sb_line)) then | | |
| 527 | - allocate(sb_line(cur_pane%cols)) | | |
| 528 | - else if (size(sb_line) /= cur_pane%cols) then | | |
| 529 | - deallocate(sb_line) | | |
| 530 | - allocate(sb_line(cur_pane%cols)) | | |
| 531 | - end if | | |
| 532 | - | | |
| 533 | - do row = 1, pane_scr%rows | | |
| 534 | - ! Cell top-left y coordinate relative to pane | | |
| 535 | - y = real(row - 1) * cell_height + pane_y_offset | | |
| 536 | - | | |
| 537 | - ! Determine if this row shows scrollback or screen content | | |
| 538 | - sb_offset = scroll_offset - row + 1 | | |
| 539 | - | | |
| 540 | - if (sb_offset > 0 .and. sb_offset <= terminal_get_scrollback_count(pane_term)) then | | |
| 541 | - ! This row shows scrollback content | | |
| 542 | - call terminal_get_scrollback_line(pane_term, sb_offset - 1, sb_line, cur_pane%cols) | | |
| 543 | - do col = 1, min(cur_pane%cols, pane_scr%cols) | | |
| 544 | - cell = sb_line(col) | | |
| 545 | - | | |
| 546 | - ! Skip continuation cells (2nd half of wide chars) | | |
| 547 | - if (cell%is_continuation) cycle | | |
| 548 | - | | |
| 549 | - x = real(col - 1) * cell_width + pane_x_offset | | |
| 550 | - | | |
| 551 | - ! Draw selection background if selected (only for active pane) | | |
| 552 | - if (cur_pane%active .and. selection_contains(sel, row, col)) then | | |
| 553 | - call renderer_draw_rect(ren, x, y, real(cell_width), real(cell_height), & | | |
| 554 | - 0.3, 0.3, 0.6, 1.0) | | |
| 555 | - end if | | |
| 556 | - | | |
| 557 | - ! Only render cells with actual text content | | |
| 558 | - if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then | | |
| 559 | - r = real(cell%fg%r) / 255.0 * dim_factor | | |
| 560 | - g = real(cell%fg%g) / 255.0 * dim_factor | | |
| 561 | - b = real(cell%fg%b) / 255.0 * dim_factor | | |
| 562 | - call renderer_draw_char(ren, x, y + real(ascender), cell%codepoint, r, g, b, 1.0) | | |
| 563 | - end if | | |
| 564 | - end do | | |
| 565 | - else | | |
| 566 | - ! This row shows screen content | | |
| 567 | - screen_row = row - scroll_offset | | |
| 568 | - if (screen_row >= 1 .and. screen_row <= pane_scr%rows) then | | |
| 569 | - do col = 1, pane_scr%cols | | |
| 570 | - cell = screen_get_cell(pane_scr, screen_row, col) | | |
| 571 | - | | |
| 572 | - ! Skip continuation cells (2nd half of wide chars) | | |
| 573 | - if (cell%is_continuation) cycle | | |
| 574 | - | | |
| 575 | - x = real(col - 1) * cell_width + pane_x_offset | | |
| 576 | - | | |
| 577 | - ! Draw selection background if selected (only for active pane) | | |
| 578 | - if (cur_pane%active .and. selection_contains(sel, row, col)) then | | |
| 579 | - call renderer_draw_rect(ren, x, y, real(cell_width), real(cell_height), & | | |
| 580 | - 0.3, 0.3, 0.6, 1.0) | | |
| 581 | - end if | | |
| 582 | - | | |
| 583 | - ! Only render cells with actual text content | | |
| 584 | - if (cell%codepoint /= 32 .and. cell%codepoint /= 0) then | | |
| 585 | - r = real(cell%fg%r) / 255.0 * dim_factor | | |
| 586 | - g = real(cell%fg%g) / 255.0 * dim_factor | | |
| 587 | - b = real(cell%fg%b) / 255.0 * dim_factor | | |
| 588 | - call renderer_draw_char(ren, x, y + real(ascender), cell%codepoint, r, g, b, 1.0) | | |
| 589 | - end if | | |
| 590 | - end do | | |
| 591 | - end if | | |
| 592 | - end if | | |
| 593 | - end do | | |
| 594 | - | | |
| 595 | - ! Draw cursor if visible (only for active pane and when not scrolled back) | | |
| 596 | - if (cur_pane%active .and. pane_term%cursor%visible .and. scroll_offset == 0) then | | |
| 597 | - ! Check blink state - only hide cursor if blink is enabled and in off phase | | |
| 598 | - if (.not. pane_term%cursor%blink .or. cursor_blink_visible) then | | |
| 599 | - x = real(pane_term%cursor%col - 1) * cell_width + pane_x_offset | | |
| 600 | - y = real(pane_term%cursor%row - 1) * cell_height + pane_y_offset | | |
| 601 | - | | |
| 602 | - select case (pane_term%cursor%style) | | |
| 603 | - case (CURSOR_BLOCK) | | |
| 604 | - call renderer_draw_rect(ren, x, y, real(cell_width), real(cell_height), & | | |
| 605 | - 0.7, 0.7, 0.7, 0.8) | | |
| 606 | - case (CURSOR_UNDERLINE) | | |
| 607 | - call renderer_draw_rect(ren, x, y + real(ascender), & | | |
| 608 | - real(cell_width), 2.0, 0.7, 0.7, 0.7, 1.0) | | |
| 609 | - case (CURSOR_BAR) | | |
| 610 | - call renderer_draw_rect(ren, x, y, 2.0, real(cell_height), 0.7, 0.7, 0.7, 1.0) | | |
| 611 | - case default | | |
| 612 | - call renderer_draw_rect(ren, x, y, real(cell_width), real(cell_height), & | | |
| 613 | - 0.7, 0.7, 0.7, 0.8) | | |
| 614 | - end select | | |
| 615 | - end if | | |
| 616 | - end if | | |
| 617 | - | | |
| 618 | - call glDisable(GL_SCISSOR_TEST) | | |
| 619 | - end do | | |
| 620 | - | | |
| 621 | - call renderer_flush(ren) | | |
| 622 | - | | |
| 623 | - ! Swap buffers | | |
| 624 | - call window_swap_buffers(win) | | |
| 625 | - end subroutine do_render | | |
| 626 | - | | |
| 627 | ! Handle tab action signals from keyboard | 441 | ! Handle tab action signals from keyboard |
| 628 | subroutine handle_tab_action(action) | 442 | subroutine handle_tab_action(action) |
| 629 | use window_mod, only: TAB_ACTION_NEW, TAB_ACTION_CLOSE, TAB_ACTION_NEXT, TAB_ACTION_PREV | 443 | use window_mod, only: TAB_ACTION_NEW, TAB_ACTION_CLOSE, TAB_ACTION_NEXT, TAB_ACTION_PREV |