fixed glow + dimming
This commit is contained in:
+53
-7
@@ -219,6 +219,43 @@ const CURSOR_BLOCK: u32 = 0u;
|
|||||||
const CURSOR_UNDERLINE: u32 = 1u;
|
const CURSOR_UNDERLINE: u32 = 1u;
|
||||||
const CURSOR_BAR: u32 = 2u;
|
const CURSOR_BAR: u32 = 2u;
|
||||||
|
|
||||||
|
// Check if a cell is within the selection range
|
||||||
|
// Selection is specified as (start_col, start_row) to (end_col, end_row), normalized
|
||||||
|
// so start <= end in reading order
|
||||||
|
fn is_cell_selected(col: u32, row: u32) -> bool {
|
||||||
|
// Check if selection is active (-1 values mean no selection)
|
||||||
|
if grid_params.selection_start_col < 0 || grid_params.selection_start_row < 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sel_start_col = u32(grid_params.selection_start_col);
|
||||||
|
let sel_start_row = u32(grid_params.selection_start_row);
|
||||||
|
let sel_end_col = u32(grid_params.selection_end_col);
|
||||||
|
let sel_end_row = u32(grid_params.selection_end_row);
|
||||||
|
|
||||||
|
// Check if cell is within row range
|
||||||
|
if row < sel_start_row || row > sel_end_row {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single row selection
|
||||||
|
if sel_start_row == sel_end_row {
|
||||||
|
return col >= sel_start_col && col <= sel_end_col;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-row selection
|
||||||
|
if row == sel_start_row {
|
||||||
|
// First row: from start_col to end of line
|
||||||
|
return col >= sel_start_col;
|
||||||
|
} else if row == sel_end_row {
|
||||||
|
// Last row: from start of line to end_col
|
||||||
|
return col <= sel_end_col;
|
||||||
|
} else {
|
||||||
|
// Middle rows: entire row is selected
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Vertex output for instanced cell rendering
|
// Vertex output for instanced cell rendering
|
||||||
struct CellVertexOutput {
|
struct CellVertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
@@ -327,8 +364,8 @@ fn vs_cell_bg(
|
|||||||
bg = tmp;
|
bg = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this cell is selected (per-cell flag set by CPU, respects xlimit)
|
// Check if this cell is selected using GridParams selection range
|
||||||
let is_selected = (attrs & ATTR_SELECTED_BIT) != 0u;
|
let is_selected = is_cell_selected(col, row);
|
||||||
if is_selected {
|
if is_selected {
|
||||||
fg = vec4<f32>(0.0, 0.0, 0.0, 1.0); // Black foreground
|
fg = vec4<f32>(0.0, 0.0, 0.0, 1.0); // Black foreground
|
||||||
bg = vec4<f32>(1.0, 1.0, 1.0, 1.0); // White background
|
bg = vec4<f32>(1.0, 1.0, 1.0, 1.0); // White background
|
||||||
@@ -346,9 +383,18 @@ fn vs_cell_bg(
|
|||||||
bg.a = 0.0;
|
bg.a = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate cursor color - use fg color (inverted from bg) for visibility
|
// Calculate cursor color
|
||||||
// For block cursor, we'll use fg as the cursor background
|
// If the cell is empty (no glyph), use default foreground color for cursor
|
||||||
var cursor_color = fg;
|
// Otherwise use the cell's foreground color
|
||||||
|
var cursor_color: vec4<f32>;
|
||||||
|
let sprite_idx = cell.sprite_idx & ~COLORED_GLYPH_FLAG;
|
||||||
|
if sprite_idx == 0u {
|
||||||
|
// Empty cell - use default foreground color for cursor
|
||||||
|
cursor_color = color_table.colors[256]; // default_fg
|
||||||
|
} else {
|
||||||
|
// Cell has a glyph - use its foreground color
|
||||||
|
cursor_color = fg;
|
||||||
|
}
|
||||||
cursor_color.a = 1.0;
|
cursor_color.a = 1.0;
|
||||||
|
|
||||||
var out: CellVertexOutput;
|
var out: CellVertexOutput;
|
||||||
@@ -444,8 +490,8 @@ fn vs_cell_glyph(
|
|||||||
bg = tmp;
|
bg = tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if this cell is selected (per-cell flag set by CPU, respects xlimit)
|
// Check if this cell is selected using GridParams selection range
|
||||||
let is_selected = (attrs & ATTR_SELECTED_BIT) != 0u;
|
let is_selected = is_cell_selected(col, row);
|
||||||
if is_selected {
|
if is_selected {
|
||||||
fg = vec4<f32>(0.0, 0.0, 0.0, 1.0); // Black foreground
|
fg = vec4<f32>(0.0, 0.0, 0.0, 1.0); // Black foreground
|
||||||
bg = vec4<f32>(1.0, 1.0, 1.0, 1.0); // White background
|
bg = vec4<f32>(1.0, 1.0, 1.0, 1.0); // White background
|
||||||
|
|||||||
+46
-23
@@ -270,19 +270,23 @@ impl Pane {
|
|||||||
/// Create a new pane with its own terminal and PTY.
|
/// Create a new pane with its own terminal and PTY.
|
||||||
fn new(cols: usize, rows: usize, scrollback_lines: usize) -> Result<Self, String> {
|
fn new(cols: usize, rows: usize, scrollback_lines: usize) -> Result<Self, String> {
|
||||||
let terminal = Terminal::new(cols, rows, scrollback_lines);
|
let terminal = Terminal::new(cols, rows, scrollback_lines);
|
||||||
let pty = Pty::spawn(None).map_err(|e| format!("Failed to spawn PTY: {}", e))?;
|
|
||||||
|
|
||||||
// Set terminal size (use default cell size estimate for initial pixel dimensions)
|
// Calculate pixel dimensions (use default cell size estimate)
|
||||||
let default_cell_width = 10u16;
|
let default_cell_width = 10u16;
|
||||||
let default_cell_height = 20u16;
|
let default_cell_height = 20u16;
|
||||||
if let Err(e) = pty.resize(
|
let width_px = cols as u16 * default_cell_width;
|
||||||
|
let height_px = rows as u16 * default_cell_height;
|
||||||
|
|
||||||
|
// Spawn PTY with initial size - this sets the size BEFORE forking,
|
||||||
|
// so the shell inherits the correct terminal dimensions immediately.
|
||||||
|
// This prevents race conditions where .zshrc runs before resize().
|
||||||
|
let pty = Pty::spawn(
|
||||||
|
None,
|
||||||
cols as u16,
|
cols as u16,
|
||||||
rows as u16,
|
rows as u16,
|
||||||
cols as u16 * default_cell_width,
|
width_px,
|
||||||
rows as u16 * default_cell_height,
|
height_px
|
||||||
) {
|
).map_err(|e| format!("Failed to spawn PTY: {}", e))?;
|
||||||
log::warn!("Failed to set initial PTY size: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
let pty_fd = pty.as_raw_fd();
|
let pty_fd = pty.as_raw_fd();
|
||||||
|
|
||||||
@@ -302,10 +306,18 @@ impl Pane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Resize the terminal and PTY.
|
/// Resize the terminal and PTY.
|
||||||
|
/// Only sends SIGWINCH to the PTY if the size actually changed.
|
||||||
fn resize(&mut self, cols: usize, rows: usize, width_px: u16, height_px: u16) {
|
fn resize(&mut self, cols: usize, rows: usize, width_px: u16, height_px: u16) {
|
||||||
|
// Check if size actually changed before sending SIGWINCH
|
||||||
|
// This prevents spurious signals that can interrupt programs like fastfetch
|
||||||
|
let size_changed = cols != self.terminal.cols || rows != self.terminal.rows;
|
||||||
|
|
||||||
self.terminal.resize(cols, rows);
|
self.terminal.resize(cols, rows);
|
||||||
if let Err(e) = self.pty.resize(cols as u16, rows as u16, width_px, height_px) {
|
|
||||||
log::warn!("Failed to resize PTY: {}", e);
|
if size_changed {
|
||||||
|
if let Err(e) = self.pty.resize(cols as u16, rows as u16, width_px, height_px) {
|
||||||
|
log::warn!("Failed to resize PTY: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -439,17 +451,19 @@ impl SplitNode {
|
|||||||
// Calculate how many cells fit
|
// Calculate how many cells fit
|
||||||
let cols = (width / cell_width).floor() as usize;
|
let cols = (width / cell_width).floor() as usize;
|
||||||
let rows = (height / cell_height).floor() as usize;
|
let rows = (height / cell_height).floor() as usize;
|
||||||
// Store actual cell-aligned dimensions (not allocated space)
|
// Store the full allocated dimensions (not just cell-aligned)
|
||||||
let actual_width = cols.max(1) as f32 * cell_width;
|
// This ensures edge glow and pane dimming cover the full pane area
|
||||||
let actual_height = rows.max(1) as f32 * cell_height;
|
|
||||||
*geometry = PaneGeometry {
|
*geometry = PaneGeometry {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: actual_width,
|
width, // Full allocated width
|
||||||
height: actual_height,
|
height, // Full allocated height
|
||||||
cols: cols.max(1),
|
cols: cols.max(1),
|
||||||
rows: rows.max(1),
|
rows: rows.max(1),
|
||||||
};
|
};
|
||||||
|
// Return cell-aligned dimensions for layout calculations
|
||||||
|
let actual_width = cols.max(1) as f32 * cell_width;
|
||||||
|
let actual_height = rows.max(1) as f32 * cell_height;
|
||||||
(actual_width, actual_height)
|
(actual_width, actual_height)
|
||||||
}
|
}
|
||||||
SplitNode::Split { horizontal, ratio, first, second } => {
|
SplitNode::Split { horizontal, ratio, first, second } => {
|
||||||
@@ -1954,14 +1968,17 @@ impl App {
|
|||||||
|
|
||||||
if !navigated {
|
if !navigated {
|
||||||
// No neighbor in that direction - trigger edge glow animation
|
// No neighbor in that direction - trigger edge glow animation
|
||||||
// Add to existing glows (don't replace) so multiple can be visible
|
// Use renderer's helper to calculate proper screen-space glow bounds
|
||||||
if let Some(geom) = active_pane_geom {
|
if let (Some(geom), Some(renderer)) = (active_pane_geom, &self.renderer) {
|
||||||
|
let (glow_x, glow_y, glow_width, glow_height) =
|
||||||
|
renderer.calculate_edge_glow_bounds(geom.x, geom.y, geom.width, geom.height);
|
||||||
|
|
||||||
self.edge_glows.push(EdgeGlow::new(
|
self.edge_glows.push(EdgeGlow::new(
|
||||||
direction,
|
direction,
|
||||||
geom.x,
|
glow_x,
|
||||||
geom.y,
|
glow_y,
|
||||||
geom.width,
|
glow_width,
|
||||||
geom.height,
|
glow_height,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2380,6 +2397,7 @@ impl ApplicationHandler<UserEvent> for App {
|
|||||||
let scroll_offset = self.get_scroll_offset();
|
let scroll_offset = self.get_scroll_offset();
|
||||||
let content_row = screen_row as isize - scroll_offset as isize;
|
let content_row = screen_row as isize - scroll_offset as isize;
|
||||||
let pos = CellPosition { col, row: content_row };
|
let pos = CellPosition { col, row: content_row };
|
||||||
|
log::debug!("Selection started at col={}, content_row={}, screen_row={}, scroll_offset={}", col, content_row, screen_row, scroll_offset);
|
||||||
if let Some(tab) = self.active_tab_mut() {
|
if let Some(tab) = self.active_tab_mut() {
|
||||||
if let Some(pane) = tab.active_pane_mut() {
|
if let Some(pane) = tab.active_pane_mut() {
|
||||||
pane.selection = Some(Selection { start: pos, end: pos });
|
pane.selection = Some(Selection { start: pos, end: pos });
|
||||||
@@ -2505,8 +2523,13 @@ impl ApplicationHandler<UserEvent> for App {
|
|||||||
|
|
||||||
// Convert selection to screen coords for this pane
|
// Convert selection to screen coords for this pane
|
||||||
let selection = if is_active {
|
let selection = if is_active {
|
||||||
pane.selection.as_ref()
|
let sel = pane.selection.as_ref()
|
||||||
.and_then(|sel| sel.to_screen_coords(scroll_offset, geom.rows))
|
.and_then(|sel| sel.to_screen_coords(scroll_offset, geom.rows));
|
||||||
|
if pane.selection.is_some() {
|
||||||
|
log::debug!("Render: pane.selection={:?}, scroll_offset={}, rows={}, screen_coords={:?}",
|
||||||
|
pane.selection, scroll_offset, geom.rows, sel);
|
||||||
|
}
|
||||||
|
sel
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
+17
-1
@@ -35,7 +35,8 @@ pub struct Pty {
|
|||||||
|
|
||||||
impl Pty {
|
impl Pty {
|
||||||
/// Creates a new PTY and spawns a shell process.
|
/// Creates a new PTY and spawns a shell process.
|
||||||
pub fn spawn(shell: Option<&str>) -> Result<Self, PtyError> {
|
/// The initial terminal size should be provided so the shell starts with the correct dimensions.
|
||||||
|
pub fn spawn(shell: Option<&str>, cols: u16, rows: u16, xpixel: u16, ypixel: u16) -> Result<Self, PtyError> {
|
||||||
// Open the PTY master
|
// Open the PTY master
|
||||||
let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)
|
let master = openpt(OpenptFlags::RDWR | OpenptFlags::NOCTTY | OpenptFlags::CLOEXEC)
|
||||||
.map_err(PtyError::OpenMaster)?;
|
.map_err(PtyError::OpenMaster)?;
|
||||||
@@ -50,6 +51,21 @@ impl Pty {
|
|||||||
// Get the slave name
|
// Get the slave name
|
||||||
let slave_name = ptsname(&master, Vec::new()).map_err(PtyError::PtsName)?;
|
let slave_name = ptsname(&master, Vec::new()).map_err(PtyError::PtsName)?;
|
||||||
|
|
||||||
|
// Set the terminal size BEFORE forking so the child inherits the correct size.
|
||||||
|
// This prevents race conditions where the shell's .zshrc runs before the parent
|
||||||
|
// can call resize(), causing programs like fastfetch to get wrong dimensions.
|
||||||
|
let winsize = libc::winsize {
|
||||||
|
ws_row: rows,
|
||||||
|
ws_col: cols,
|
||||||
|
ws_xpixel: xpixel,
|
||||||
|
ws_ypixel: ypixel,
|
||||||
|
};
|
||||||
|
let fd = std::os::fd::AsRawFd::as_raw_fd(&master);
|
||||||
|
let result = unsafe { libc::ioctl(fd, libc::TIOCSWINSZ, &winsize) };
|
||||||
|
if result == -1 {
|
||||||
|
return Err(PtyError::Io(std::io::Error::last_os_error()));
|
||||||
|
}
|
||||||
|
|
||||||
// Fork the process
|
// Fork the process
|
||||||
// SAFETY: We're careful to only use async-signal-safe functions in the child
|
// SAFETY: We're careful to only use async-signal-safe functions in the child
|
||||||
let fork_result = unsafe { libc::fork() };
|
let fork_result = unsafe { libc::fork() };
|
||||||
|
|||||||
+277
-38
@@ -786,6 +786,10 @@ pub struct Renderer {
|
|||||||
|
|
||||||
/// GPU quads for overlay rendering (rendered on top of everything).
|
/// GPU quads for overlay rendering (rendered on top of everything).
|
||||||
overlay_quads: Vec<Quad>,
|
overlay_quads: Vec<Quad>,
|
||||||
|
/// GPU buffer for overlay quad instances (separate from main quads).
|
||||||
|
overlay_quad_buffer: wgpu::Buffer,
|
||||||
|
/// Bind group for overlay quad rendering.
|
||||||
|
overlay_quad_bind_group: wgpu::BindGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -2747,6 +2751,30 @@ impl Renderer {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Overlay quad buffer for instance data (separate from main quads)
|
||||||
|
let overlay_quad_buffer = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
|
label: Some("Overlay Quad Buffer"),
|
||||||
|
size: (max_quads * std::mem::size_of::<Quad>()) as u64,
|
||||||
|
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
|
||||||
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Bind group for overlay quad rendering (uses same params buffer but different quad buffer)
|
||||||
|
let overlay_quad_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
label: Some("Overlay Quad Bind Group"),
|
||||||
|
layout: &quad_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: quad_params_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: overlay_quad_buffer.as_entire_binding(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
// Pipeline layout for quad rendering
|
// Pipeline layout for quad rendering
|
||||||
let quad_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
let quad_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("Quad Pipeline Layout"),
|
label: Some("Quad Pipeline Layout"),
|
||||||
@@ -2890,6 +2918,8 @@ impl Renderer {
|
|||||||
quad_pipeline,
|
quad_pipeline,
|
||||||
quad_bind_group,
|
quad_bind_group,
|
||||||
overlay_quads: Vec::with_capacity(32),
|
overlay_quads: Vec::with_capacity(32),
|
||||||
|
overlay_quad_buffer,
|
||||||
|
overlay_quad_bind_group,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2992,6 +3022,120 @@ impl Renderer {
|
|||||||
(available_height - used_height) / 2.0
|
(available_height - used_height) / 2.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Calculates screen-space bounds for edge glow given pane geometry.
|
||||||
|
/// Takes pane coordinates in grid-relative space and transforms them to screen coordinates,
|
||||||
|
/// extending to fill the terminal grid area (but not into tab bar or statusline).
|
||||||
|
/// Returns (screen_x, screen_y, width, height) for the glow mask area.
|
||||||
|
pub fn calculate_edge_glow_bounds(&self, pane_x: f32, pane_y: f32, pane_width: f32, pane_height: f32) -> (f32, f32, f32, f32) {
|
||||||
|
let grid_x_offset = self.grid_x_offset();
|
||||||
|
let grid_y_offset = self.grid_y_offset();
|
||||||
|
let terminal_y_offset = self.terminal_y_offset();
|
||||||
|
let (available_width, available_height) = self.available_grid_space();
|
||||||
|
|
||||||
|
// Calculate the terminal grid area boundaries in screen coordinates
|
||||||
|
// This is the area where content is rendered, excluding tab bar and statusline
|
||||||
|
let grid_top = terminal_y_offset;
|
||||||
|
let grid_bottom = terminal_y_offset + available_height;
|
||||||
|
let grid_left = 0.0_f32;
|
||||||
|
let grid_right = self.width as f32;
|
||||||
|
|
||||||
|
log::debug!("calculate_edge_glow_bounds: pane=({}, {}, {}, {})", pane_x, pane_y, pane_width, pane_height);
|
||||||
|
log::debug!(" grid area: top={}, bottom={}, left={}, right={}", grid_top, grid_bottom, grid_left, grid_right);
|
||||||
|
log::debug!(" offsets: grid_x={}, grid_y={}, terminal_y={}", grid_x_offset, grid_y_offset, terminal_y_offset);
|
||||||
|
|
||||||
|
// Transform pane coordinates to screen space (same as border rendering)
|
||||||
|
let mut screen_x = grid_x_offset + pane_x;
|
||||||
|
let mut screen_y = terminal_y_offset + grid_y_offset + pane_y;
|
||||||
|
let mut width = pane_width;
|
||||||
|
let mut height = pane_height;
|
||||||
|
|
||||||
|
log::debug!(" initial screen: ({}, {}, {}, {})", screen_x, screen_y, width, height);
|
||||||
|
|
||||||
|
// Use a larger epsilon to account for cell-alignment gaps in split panes
|
||||||
|
// With cell-aligned splits, gaps can be up to one cell height
|
||||||
|
let epsilon = self.cell_height.max(self.cell_width);
|
||||||
|
|
||||||
|
// Left edge at screen boundary - extend to screen left edge
|
||||||
|
if pane_x < epsilon {
|
||||||
|
width += screen_x - grid_left;
|
||||||
|
screen_x = grid_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right edge at screen boundary - extend to screen right edge
|
||||||
|
if (pane_x + pane_width) >= available_width - epsilon {
|
||||||
|
width = grid_right - screen_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top edge at grid boundary - extend to grid top (respects tab bar/statusline at top)
|
||||||
|
if pane_y < epsilon {
|
||||||
|
height += screen_y - grid_top;
|
||||||
|
screen_y = grid_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom edge at grid boundary - extend to grid bottom (respects tab bar/statusline at bottom)
|
||||||
|
if (pane_y + pane_height) >= available_height - epsilon {
|
||||||
|
height = grid_bottom - screen_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!(" final screen: ({}, {}, {}, {})", screen_x, screen_y, width, height);
|
||||||
|
|
||||||
|
(screen_x, screen_y, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates screen-space bounds for dim overlay given pane geometry.
|
||||||
|
/// Takes pane coordinates in grid-relative space and transforms them to screen coordinates,
|
||||||
|
/// extending to fill the terminal grid area (but not into tab bar or statusline).
|
||||||
|
/// This is identical to calculate_edge_glow_bounds - ensures dim overlays cover the full
|
||||||
|
/// pane area including centering margins, matching edge glow behavior.
|
||||||
|
/// Returns (screen_x, screen_y, width, height) for the overlay area.
|
||||||
|
pub fn calculate_dim_overlay_bounds(&self, pane_x: f32, pane_y: f32, pane_width: f32, pane_height: f32) -> (f32, f32, f32, f32) {
|
||||||
|
let grid_x_offset = self.grid_x_offset();
|
||||||
|
let grid_y_offset = self.grid_y_offset();
|
||||||
|
let terminal_y_offset = self.terminal_y_offset();
|
||||||
|
let (available_width, available_height) = self.available_grid_space();
|
||||||
|
|
||||||
|
// Calculate the terminal grid area boundaries in screen coordinates
|
||||||
|
// This is the area where content is rendered, excluding tab bar and statusline
|
||||||
|
let grid_top = terminal_y_offset;
|
||||||
|
let grid_bottom = terminal_y_offset + available_height;
|
||||||
|
let grid_left = 0.0_f32;
|
||||||
|
let grid_right = self.width as f32;
|
||||||
|
|
||||||
|
// Transform pane coordinates to screen space (same as border rendering)
|
||||||
|
let mut screen_x = grid_x_offset + pane_x;
|
||||||
|
let mut screen_y = terminal_y_offset + grid_y_offset + pane_y;
|
||||||
|
let mut width = pane_width;
|
||||||
|
let mut height = pane_height;
|
||||||
|
|
||||||
|
// Use a larger epsilon to account for cell-alignment gaps in split panes
|
||||||
|
// With cell-aligned splits, gaps can be up to one cell height
|
||||||
|
let epsilon = self.cell_height.max(self.cell_width);
|
||||||
|
|
||||||
|
// Left edge at screen boundary - extend to screen left edge
|
||||||
|
if pane_x < epsilon {
|
||||||
|
width += screen_x - grid_left;
|
||||||
|
screen_x = grid_left;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Right edge at screen boundary - extend to screen right edge
|
||||||
|
if (pane_x + pane_width) >= available_width - epsilon {
|
||||||
|
width = grid_right - screen_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top edge at grid boundary - extend to grid top (respects tab bar/statusline at top)
|
||||||
|
if pane_y < epsilon {
|
||||||
|
height += screen_y - grid_top;
|
||||||
|
screen_y = grid_top;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottom edge at grid boundary - extend to grid bottom (respects tab bar/statusline at bottom)
|
||||||
|
if (pane_y + pane_height) >= available_height - epsilon {
|
||||||
|
height = grid_bottom - screen_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
(screen_x, screen_y, width, height)
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts a pixel position to a terminal cell position.
|
/// Converts a pixel position to a terminal cell position.
|
||||||
/// Returns None if the position is outside the terminal area (e.g., in the tab bar or statusline).
|
/// Returns None if the position is outside the terminal area (e.g., in the tab bar or statusline).
|
||||||
pub fn pixel_to_cell(&self, x: f64, y: f64) -> Option<(usize, usize)> {
|
pub fn pixel_to_cell(&self, x: f64, y: f64) -> Option<(usize, usize)> {
|
||||||
@@ -4055,8 +4199,10 @@ impl Renderer {
|
|||||||
self.statusline_gpu_cells.clear();
|
self.statusline_gpu_cells.clear();
|
||||||
|
|
||||||
// Calculate target columns based on window width
|
// Calculate target columns based on window width
|
||||||
|
// Use ceil() to ensure we cover the entire window edge-to-edge
|
||||||
|
// (the rightmost cell may extend slightly past the window, which is fine)
|
||||||
let target_cols = if self.cell_width > 0.0 {
|
let target_cols = if self.cell_width > 0.0 {
|
||||||
(target_width / self.cell_width).floor() as usize
|
(target_width / self.cell_width).ceil() as usize
|
||||||
} else {
|
} else {
|
||||||
self.statusline_max_cols
|
self.statusline_max_cols
|
||||||
};
|
};
|
||||||
@@ -4224,9 +4370,16 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
StatuslineContent::Sections(sections) => {
|
StatuslineContent::Sections(sections) => {
|
||||||
for section in sections.iter() {
|
for (section_idx, section) in sections.iter().enumerate() {
|
||||||
let section_bg = Self::pack_statusline_color(section.bg);
|
let section_bg = Self::pack_statusline_color(section.bg);
|
||||||
|
|
||||||
|
// Get next section's background for powerline arrow transition
|
||||||
|
let next_section_bg = if section_idx + 1 < sections.len() {
|
||||||
|
Self::pack_statusline_color(sections[section_idx + 1].bg)
|
||||||
|
} else {
|
||||||
|
default_bg
|
||||||
|
};
|
||||||
|
|
||||||
for component in section.components.iter() {
|
for component in section.components.iter() {
|
||||||
let component_fg = Self::pack_statusline_color(component.fg);
|
let component_fg = Self::pack_statusline_color(component.fg);
|
||||||
let style = if component.bold { FontStyle::Bold } else { FontStyle::Regular };
|
let style = if component.bold { FontStyle::Bold } else { FontStyle::Regular };
|
||||||
@@ -4358,10 +4511,10 @@ impl Renderer {
|
|||||||
let arrow_char = '\u{E0B0}';
|
let arrow_char = '\u{E0B0}';
|
||||||
let (sprite_idx, _) = self.get_or_create_sprite_for(arrow_char, FontStyle::Regular, SpriteTarget::Statusline);
|
let (sprite_idx, _) = self.get_or_create_sprite_for(arrow_char, FontStyle::Regular, SpriteTarget::Statusline);
|
||||||
|
|
||||||
// Arrow foreground is section background, arrow background is next section bg or default
|
// Arrow foreground is current section's bg, arrow background is next section's bg
|
||||||
self.statusline_gpu_cells.push(GPUCell {
|
self.statusline_gpu_cells.push(GPUCell {
|
||||||
fg: section_bg, // Arrow takes section bg color as its foreground
|
fg: section_bg, // Arrow takes section bg color as its foreground
|
||||||
bg: default_bg, // Will be overwritten if there's a next section
|
bg: next_section_bg, // Background is the next section's background
|
||||||
decoration_fg: 0,
|
decoration_fg: 0,
|
||||||
sprite_idx,
|
sprite_idx,
|
||||||
attrs: 0,
|
attrs: 0,
|
||||||
@@ -4371,6 +4524,19 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill remaining width with default background cells
|
||||||
|
// This ensures the statusline covers the entire window width
|
||||||
|
let default_bg_packed = Self::pack_statusline_color(StatuslineColor::Default);
|
||||||
|
while self.statusline_gpu_cells.len() < target_cols && self.statusline_gpu_cells.len() < self.statusline_max_cols {
|
||||||
|
self.statusline_gpu_cells.push(GPUCell {
|
||||||
|
fg: 0,
|
||||||
|
bg: default_bg_packed,
|
||||||
|
decoration_fg: 0,
|
||||||
|
sprite_idx: 0,
|
||||||
|
attrs: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
self.statusline_gpu_cells.len()
|
self.statusline_gpu_cells.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6705,6 +6871,7 @@ impl Renderer {
|
|||||||
Direction::Right => 3,
|
Direction::Right => 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Glow coordinates are already in screen space (transformed by calculate_edge_glow_bounds)
|
||||||
glow_instances[i] = GlowInstance {
|
glow_instances[i] = GlowInstance {
|
||||||
direction,
|
direction,
|
||||||
progress: glow.progress(),
|
progress: glow.progress(),
|
||||||
@@ -6753,6 +6920,10 @@ impl Renderer {
|
|||||||
// Sync palette from first terminal
|
// Sync palette from first terminal
|
||||||
if let Some((terminal, _, _)) = panes.first() {
|
if let Some((terminal, _, _)) = panes.first() {
|
||||||
self.palette = terminal.palette.clone();
|
self.palette = terminal.palette.clone();
|
||||||
|
log::debug!("render_panes: synced palette from first terminal, default_bg={:?}, default_fg={:?}",
|
||||||
|
self.palette.default_bg, self.palette.default_fg);
|
||||||
|
} else {
|
||||||
|
log::debug!("render_panes: no panes, using existing palette");
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
@@ -6794,19 +6965,20 @@ impl Renderer {
|
|||||||
TabBarPosition::Hidden => unreachable!(),
|
TabBarPosition::Hidden => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tab_bar_bg = {
|
// Use same color as statusline: 0x1a1a1a (26, 26, 26) in sRGB
|
||||||
let [r, g, b] = self.palette.default_bg;
|
// Linear RGB value: ~0.00972
|
||||||
let factor = 0.85_f32;
|
let tab_bar_bg = [
|
||||||
[
|
Self::srgb_to_linear(26.0 / 255.0),
|
||||||
Self::srgb_to_linear((r as f32 / 255.0) * factor),
|
Self::srgb_to_linear(26.0 / 255.0),
|
||||||
Self::srgb_to_linear((g as f32 / 255.0) * factor),
|
Self::srgb_to_linear(26.0 / 255.0),
|
||||||
Self::srgb_to_linear((b as f32 / 255.0) * factor),
|
1.0,
|
||||||
1.0,
|
];
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw tab bar background
|
// Draw tab bar background
|
||||||
|
log::debug!("render_panes: drawing tab bar at y={}, height={}, num_tabs={}, quads_before={}",
|
||||||
|
tab_bar_y, tab_bar_height, num_tabs, self.quads.len());
|
||||||
self.render_rect(0.0, tab_bar_y, width, tab_bar_height, tab_bar_bg);
|
self.render_rect(0.0, tab_bar_y, width, tab_bar_height, tab_bar_bg);
|
||||||
|
log::debug!("render_panes: after tab bar rect, quads_count={}", self.quads.len());
|
||||||
|
|
||||||
// Render each tab
|
// Render each tab
|
||||||
let mut tab_x = 4.0_f32;
|
let mut tab_x = 4.0_f32;
|
||||||
@@ -6820,15 +6992,25 @@ impl Renderer {
|
|||||||
let tab_width = title_width.max(min_tab_width);
|
let tab_width = title_width.max(min_tab_width);
|
||||||
|
|
||||||
let tab_bg = if is_active {
|
let tab_bg = if is_active {
|
||||||
|
// Active tab: brightest - significantly brighter than tab bar
|
||||||
let [r, g, b] = self.palette.default_bg;
|
let [r, g, b] = self.palette.default_bg;
|
||||||
|
let boost = 50.0_f32; // More visible for active tab
|
||||||
[
|
[
|
||||||
Self::srgb_to_linear(r as f32 / 255.0),
|
Self::srgb_to_linear((r as f32 + boost).min(255.0) / 255.0),
|
||||||
Self::srgb_to_linear(g as f32 / 255.0),
|
Self::srgb_to_linear((g as f32 + boost).min(255.0) / 255.0),
|
||||||
Self::srgb_to_linear(b as f32 / 255.0),
|
Self::srgb_to_linear((b as f32 + boost).min(255.0) / 255.0),
|
||||||
1.0,
|
1.0,
|
||||||
]
|
]
|
||||||
} else {
|
} else {
|
||||||
tab_bar_bg
|
// Inactive tab: slightly brighter than tab bar background
|
||||||
|
let [r, g, b] = self.palette.default_bg;
|
||||||
|
let boost = 30.0_f32;
|
||||||
|
[
|
||||||
|
Self::srgb_to_linear((r as f32 + boost).min(255.0) / 255.0),
|
||||||
|
Self::srgb_to_linear((g as f32 + boost).min(255.0) / 255.0),
|
||||||
|
Self::srgb_to_linear((b as f32 + boost).min(255.0) / 255.0),
|
||||||
|
1.0,
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
let tab_fg = {
|
let tab_fg = {
|
||||||
@@ -6934,6 +7116,15 @@ impl Renderer {
|
|||||||
// Tolerance for detecting adjacent panes (should be touching or very close)
|
// Tolerance for detecting adjacent panes (should be touching or very close)
|
||||||
let adjacency_tolerance = 1.0;
|
let adjacency_tolerance = 1.0;
|
||||||
|
|
||||||
|
// Calculate grid boundaries for extending borders to screen edges
|
||||||
|
// Same technique as edge glow and dim overlay
|
||||||
|
let (available_width, available_height) = self.available_grid_space();
|
||||||
|
let grid_top = terminal_y_offset;
|
||||||
|
let grid_bottom = terminal_y_offset + available_height;
|
||||||
|
let grid_left = 0.0_f32;
|
||||||
|
let grid_right = width;
|
||||||
|
let epsilon = self.cell_height.max(self.cell_width);
|
||||||
|
|
||||||
// Check each pair of panes to find adjacent ones
|
// Check each pair of panes to find adjacent ones
|
||||||
for i in 0..panes.len() {
|
for i in 0..panes.len() {
|
||||||
for j in (i + 1)..panes.len() {
|
for j in (i + 1)..panes.len() {
|
||||||
@@ -6962,9 +7153,19 @@ impl Renderer {
|
|||||||
// Pane A is to the left of pane B (A's right edge touches B's left edge)
|
// Pane A is to the left of pane B (A's right edge touches B's left edge)
|
||||||
if (a_right - b_x).abs() < adjacency_tolerance {
|
if (a_right - b_x).abs() < adjacency_tolerance {
|
||||||
// Check if they overlap vertically
|
// Check if they overlap vertically
|
||||||
let top = a_y.max(b_y);
|
let mut top = a_y.max(b_y);
|
||||||
let bottom = a_bottom.min(b_bottom);
|
let mut bottom = a_bottom.min(b_bottom);
|
||||||
if bottom > top {
|
if bottom > top {
|
||||||
|
// Extend to grid edges if both panes reach the edge
|
||||||
|
// Top edge: extend if both panes are at grid top
|
||||||
|
if info_a.y < epsilon && info_b.y < epsilon {
|
||||||
|
top = grid_top;
|
||||||
|
}
|
||||||
|
// Bottom edge: extend if both panes reach grid bottom
|
||||||
|
if (info_a.y + info_a.height) >= available_height - epsilon
|
||||||
|
&& (info_b.y + info_b.height) >= available_height - epsilon {
|
||||||
|
bottom = grid_bottom;
|
||||||
|
}
|
||||||
// Draw vertical border centered on their shared edge
|
// Draw vertical border centered on their shared edge
|
||||||
let border_x = a_right - border_thickness / 2.0;
|
let border_x = a_right - border_thickness / 2.0;
|
||||||
self.render_overlay_rect(border_x, top, border_thickness, bottom - top, border_color);
|
self.render_overlay_rect(border_x, top, border_thickness, bottom - top, border_color);
|
||||||
@@ -6972,9 +7173,17 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
// Pane B is to the left of pane A
|
// Pane B is to the left of pane A
|
||||||
if (b_right - a_x).abs() < adjacency_tolerance {
|
if (b_right - a_x).abs() < adjacency_tolerance {
|
||||||
let top = a_y.max(b_y);
|
let mut top = a_y.max(b_y);
|
||||||
let bottom = a_bottom.min(b_bottom);
|
let mut bottom = a_bottom.min(b_bottom);
|
||||||
if bottom > top {
|
if bottom > top {
|
||||||
|
// Extend to grid edges if both panes reach the edge
|
||||||
|
if info_a.y < epsilon && info_b.y < epsilon {
|
||||||
|
top = grid_top;
|
||||||
|
}
|
||||||
|
if (info_a.y + info_a.height) >= available_height - epsilon
|
||||||
|
&& (info_b.y + info_b.height) >= available_height - epsilon {
|
||||||
|
bottom = grid_bottom;
|
||||||
|
}
|
||||||
let border_x = b_right - border_thickness / 2.0;
|
let border_x = b_right - border_thickness / 2.0;
|
||||||
self.render_overlay_rect(border_x, top, border_thickness, bottom - top, border_color);
|
self.render_overlay_rect(border_x, top, border_thickness, bottom - top, border_color);
|
||||||
}
|
}
|
||||||
@@ -6984,9 +7193,19 @@ impl Renderer {
|
|||||||
// Pane A is above pane B (A's bottom edge touches B's top edge)
|
// Pane A is above pane B (A's bottom edge touches B's top edge)
|
||||||
if (a_bottom - b_y).abs() < adjacency_tolerance {
|
if (a_bottom - b_y).abs() < adjacency_tolerance {
|
||||||
// Check if they overlap horizontally
|
// Check if they overlap horizontally
|
||||||
let left = a_x.max(b_x);
|
let mut left = a_x.max(b_x);
|
||||||
let right = a_right.min(b_right);
|
let mut right = a_right.min(b_right);
|
||||||
if right > left {
|
if right > left {
|
||||||
|
// Extend to screen edges if both panes reach the edge
|
||||||
|
// Left edge: extend if both panes are at grid left
|
||||||
|
if info_a.x < epsilon && info_b.x < epsilon {
|
||||||
|
left = grid_left;
|
||||||
|
}
|
||||||
|
// Right edge: extend if both panes reach grid right
|
||||||
|
if (info_a.x + info_a.width) >= available_width - epsilon
|
||||||
|
&& (info_b.x + info_b.width) >= available_width - epsilon {
|
||||||
|
right = grid_right;
|
||||||
|
}
|
||||||
// Draw horizontal border centered on their shared edge
|
// Draw horizontal border centered on their shared edge
|
||||||
let border_y = a_bottom - border_thickness / 2.0;
|
let border_y = a_bottom - border_thickness / 2.0;
|
||||||
self.render_overlay_rect(left, border_y, right - left, border_thickness, border_color);
|
self.render_overlay_rect(left, border_y, right - left, border_thickness, border_color);
|
||||||
@@ -6994,9 +7213,17 @@ impl Renderer {
|
|||||||
}
|
}
|
||||||
// Pane B is above pane A
|
// Pane B is above pane A
|
||||||
if (b_bottom - a_y).abs() < adjacency_tolerance {
|
if (b_bottom - a_y).abs() < adjacency_tolerance {
|
||||||
let left = a_x.max(b_x);
|
let mut left = a_x.max(b_x);
|
||||||
let right = a_right.min(b_right);
|
let mut right = a_right.min(b_right);
|
||||||
if right > left {
|
if right > left {
|
||||||
|
// Extend to screen edges if both panes reach the edge
|
||||||
|
if info_a.x < epsilon && info_b.x < epsilon {
|
||||||
|
left = grid_left;
|
||||||
|
}
|
||||||
|
if (info_a.x + info_a.width) >= available_width - epsilon
|
||||||
|
&& (info_b.x + info_b.width) >= available_width - epsilon {
|
||||||
|
right = grid_right;
|
||||||
|
}
|
||||||
let border_y = b_bottom - border_thickness / 2.0;
|
let border_y = b_bottom - border_thickness / 2.0;
|
||||||
self.render_overlay_rect(left, border_y, right - left, border_thickness, border_color);
|
self.render_overlay_rect(left, border_y, right - left, border_thickness, border_color);
|
||||||
}
|
}
|
||||||
@@ -7027,6 +7254,9 @@ impl Renderer {
|
|||||||
let pane_width = info.width;
|
let pane_width = info.width;
|
||||||
let pane_height = info.height;
|
let pane_height = info.height;
|
||||||
|
|
||||||
|
log::debug!("render_panes: pane {} at ({}, {}), size {}x{}, bottom_edge={}",
|
||||||
|
info.pane_id, pane_x, pane_y, pane_width, pane_height, pane_y + pane_height);
|
||||||
|
|
||||||
// Update GPU cells for this terminal (populates self.gpu_cells)
|
// Update GPU cells for this terminal (populates self.gpu_cells)
|
||||||
self.update_gpu_cells(terminal);
|
self.update_gpu_cells(terminal);
|
||||||
|
|
||||||
@@ -7056,8 +7286,9 @@ impl Renderer {
|
|||||||
screen_height: self.height as f32,
|
screen_height: self.height as f32,
|
||||||
x_offset: pane_x,
|
x_offset: pane_x,
|
||||||
y_offset: pane_y,
|
y_offset: pane_y,
|
||||||
cursor_col: if terminal.cursor_visible { terminal.cursor_col as i32 } else { -1 },
|
// Hide cursor when scrolled into scrollback buffer or when cursor is explicitly hidden
|
||||||
cursor_row: if terminal.cursor_visible { terminal.cursor_row as i32 } else { -1 },
|
cursor_col: if terminal.cursor_visible && terminal.scroll_offset == 0 { terminal.cursor_col as i32 } else { -1 },
|
||||||
|
cursor_row: if terminal.cursor_visible && terminal.scroll_offset == 0 { terminal.cursor_row as i32 } else { -1 },
|
||||||
cursor_style: match terminal.cursor_shape {
|
cursor_style: match terminal.cursor_shape {
|
||||||
CursorShape::BlinkingBlock | CursorShape::SteadyBlock => 0,
|
CursorShape::BlinkingBlock | CursorShape::SteadyBlock => 0,
|
||||||
CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline => 1,
|
CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline => 1,
|
||||||
@@ -7098,11 +7329,14 @@ impl Renderer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build dim overlay if needed
|
// Build dim overlay if needed - use calculate_dim_overlay_bounds to extend
|
||||||
|
// edge panes to fill the terminal grid area (matching edge glow behavior)
|
||||||
let dim_overlay = if info.dim_factor < 1.0 {
|
let dim_overlay = if info.dim_factor < 1.0 {
|
||||||
let overlay_alpha = 1.0 - info.dim_factor;
|
let overlay_alpha = 1.0 - info.dim_factor;
|
||||||
let overlay_color = [0.0, 0.0, 0.0, overlay_alpha];
|
let overlay_color = [0.0, 0.0, 0.0, overlay_alpha];
|
||||||
Some((pane_x, pane_y, pane_width, pane_height, overlay_color))
|
// Pass raw grid-relative coordinates, the helper transforms to screen space
|
||||||
|
let (ox, oy, ow, oh) = self.calculate_dim_overlay_bounds(info.x, info.y, info.width, info.height);
|
||||||
|
Some((ox, oy, ow, oh, overlay_color))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -7447,12 +7681,9 @@ impl Renderer {
|
|||||||
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||||
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||||
|
|
||||||
// Draw bg + glyph indices (tab bar text uses legacy vertex rendering)
|
|
||||||
render_pass.draw_indexed(0..total_index_count as u32, 0, 0..1);
|
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
// INSTANCED QUAD RENDERING (tab bar backgrounds, borders, etc.)
|
// INSTANCED QUAD RENDERING (tab bar backgrounds, borders, etc.)
|
||||||
// Rendered before cell content so backgrounds appear behind cells
|
// Rendered FIRST so backgrounds appear behind text
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
if !self.quads.is_empty() {
|
if !self.quads.is_empty() {
|
||||||
render_pass.set_pipeline(&self.quad_pipeline);
|
render_pass.set_pipeline(&self.quad_pipeline);
|
||||||
@@ -7460,6 +7691,14 @@ impl Renderer {
|
|||||||
render_pass.draw(0..4, 0..self.quads.len() as u32);
|
render_pass.draw(0..4, 0..self.quads.len() as u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw bg + glyph indices (tab bar text uses legacy vertex rendering)
|
||||||
|
// Rendered AFTER quads so text appears on top of backgrounds
|
||||||
|
render_pass.set_pipeline(&self.glyph_pipeline);
|
||||||
|
render_pass.set_bind_group(0, &self.glyph_bind_group, &[]);
|
||||||
|
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
||||||
|
render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||||
|
render_pass.draw_indexed(0..total_index_count as u32, 0, 0..1);
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
// INSTANCED CELL RENDERING (Like Kitty's per-window VAO approach)
|
// INSTANCED CELL RENDERING (Like Kitty's per-window VAO approach)
|
||||||
// Each pane has its own bind group with its own buffers.
|
// Each pane has its own bind group with its own buffers.
|
||||||
@@ -7518,10 +7757,10 @@ impl Renderer {
|
|||||||
// Rendered last so overlays appear on top of everything
|
// Rendered last so overlays appear on top of everything
|
||||||
// ═══════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════
|
||||||
if !self.overlay_quads.is_empty() {
|
if !self.overlay_quads.is_empty() {
|
||||||
// Upload overlay quads to the buffer (reusing the same buffer)
|
// Upload overlay quads to the SEPARATE overlay buffer to avoid overwriting tab bar quads
|
||||||
self.queue.write_buffer(&self.quad_buffer, 0, bytemuck::cast_slice(&self.overlay_quads));
|
self.queue.write_buffer(&self.overlay_quad_buffer, 0, bytemuck::cast_slice(&self.overlay_quads));
|
||||||
render_pass.set_pipeline(&self.quad_pipeline);
|
render_pass.set_pipeline(&self.quad_pipeline);
|
||||||
render_pass.set_bind_group(0, &self.quad_bind_group, &[]);
|
render_pass.set_bind_group(0, &self.overlay_quad_bind_group, &[]);
|
||||||
render_pass.draw(0..4, 0..self.overlay_quads.len() as u32);
|
render_pass.draw(0..4, 0..self.overlay_quads.len() as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ struct VertexOutput {
|
|||||||
// HELPER FUNCTIONS
|
// HELPER FUNCTIONS
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
// Statusline default background color (0x1a1a1a in linear RGB)
|
||||||
|
const STATUSLINE_DEFAULT_BG: vec3<f32> = vec3<f32>(0.00972, 0.00972, 0.00972);
|
||||||
|
|
||||||
// Resolve a packed color to RGBA
|
// Resolve a packed color to RGBA
|
||||||
fn resolve_color(packed: u32, is_foreground: bool) -> vec4<f32> {
|
fn resolve_color(packed: u32, is_foreground: bool) -> vec4<f32> {
|
||||||
let color_type = packed & 0xFFu;
|
let color_type = packed & 0xFFu;
|
||||||
@@ -152,7 +155,8 @@ fn resolve_color(packed: u32, is_foreground: bool) -> vec4<f32> {
|
|||||||
if is_foreground {
|
if is_foreground {
|
||||||
return color_table.colors[256];
|
return color_table.colors[256];
|
||||||
} else {
|
} else {
|
||||||
return color_table.colors[257];
|
// Statusline uses a solid default background, not the terminal's transparent one
|
||||||
|
return vec4<f32>(STATUSLINE_DEFAULT_BG, 1.0);
|
||||||
}
|
}
|
||||||
} else if color_type == COLOR_TYPE_INDEXED {
|
} else if color_type == COLOR_TYPE_INDEXED {
|
||||||
let index = (packed >> 8u) & 0xFFu;
|
let index = (packed >> 8u) & 0xFFu;
|
||||||
@@ -207,13 +211,9 @@ fn vs_statusline_bg(
|
|||||||
let ndc_pos = pixel_to_ndc(positions[vertex_index], screen_size);
|
let ndc_pos = pixel_to_ndc(positions[vertex_index], screen_size);
|
||||||
|
|
||||||
let fg = resolve_color(cell.fg, true);
|
let fg = resolve_color(cell.fg, true);
|
||||||
var bg = resolve_color(cell.bg, false);
|
let bg = resolve_color(cell.bg, false);
|
||||||
|
|
||||||
// For default background, use transparent
|
// Statusline always has solid background (no transparency for default bg)
|
||||||
let bg_type = cell.bg & 0xFFu;
|
|
||||||
if bg_type == COLOR_TYPE_DEFAULT {
|
|
||||||
bg.a = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var out: VertexOutput;
|
var out: VertexOutput;
|
||||||
out.clip_position = vec4<f32>(ndc_pos, 0.0, 1.0);
|
out.clip_position = vec4<f32>(ndc_pos, 0.0, 1.0);
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ impl Terminal {
|
|||||||
|
|
||||||
/// Creates a new terminal with the given dimensions and scrollback limit.
|
/// Creates a new terminal with the given dimensions and scrollback limit.
|
||||||
pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
|
pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
|
||||||
|
log::info!("Terminal::new: cols={}, rows={}, scroll_bottom={}", cols, rows, rows.saturating_sub(1));
|
||||||
let grid = vec![vec![Cell::default(); cols]; rows];
|
let grid = vec![vec![Cell::default(); cols]; rows];
|
||||||
let line_map: Vec<usize> = (0..rows).collect();
|
let line_map: Vec<usize> = (0..rows).collect();
|
||||||
|
|
||||||
@@ -704,6 +705,8 @@ impl Terminal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log::info!("Terminal::resize: {}x{} -> {}x{}", self.cols, self.rows, cols, rows);
|
||||||
|
|
||||||
let old_cols = self.cols;
|
let old_cols = self.cols;
|
||||||
let old_rows = self.rows;
|
let old_rows = self.rows;
|
||||||
|
|
||||||
@@ -1260,10 +1263,12 @@ impl Handler for Terminal {
|
|||||||
}
|
}
|
||||||
// Line feed, Vertical tab, Form feed
|
// Line feed, Vertical tab, Form feed
|
||||||
'\x0A' | '\x0B' | '\x0C' => {
|
'\x0A' | '\x0B' | '\x0C' => {
|
||||||
|
let old_row = self.cursor_row;
|
||||||
self.cursor_row += 1;
|
self.cursor_row += 1;
|
||||||
if self.cursor_row > self.scroll_bottom {
|
if self.cursor_row > self.scroll_bottom {
|
||||||
self.scroll_up(1);
|
self.scroll_up(1);
|
||||||
self.cursor_row = self.scroll_bottom;
|
self.cursor_row = self.scroll_bottom;
|
||||||
|
log::trace!("LF: scrolled at row {}, now at scroll_bottom {}", old_row, self.cursor_row);
|
||||||
}
|
}
|
||||||
// Update cache after line change
|
// Update cache after line change
|
||||||
cached_row = self.cursor_row;
|
cached_row = self.cursor_row;
|
||||||
@@ -1609,17 +1614,23 @@ impl Handler for Terminal {
|
|||||||
// Cursor Up
|
// Cursor Up
|
||||||
'A' => {
|
'A' => {
|
||||||
let n = params.get(0, 1).max(1) as usize;
|
let n = params.get(0, 1).max(1) as usize;
|
||||||
|
let old_row = self.cursor_row;
|
||||||
self.cursor_row = self.cursor_row.saturating_sub(n);
|
self.cursor_row = self.cursor_row.saturating_sub(n);
|
||||||
|
log::trace!("CSI A: cursor up {} from row {} to {}", n, old_row, self.cursor_row);
|
||||||
}
|
}
|
||||||
// Cursor Down
|
// Cursor Down
|
||||||
'B' => {
|
'B' => {
|
||||||
let n = params.get(0, 1).max(1) as usize;
|
let n = params.get(0, 1).max(1) as usize;
|
||||||
|
let old_row = self.cursor_row;
|
||||||
self.cursor_row = (self.cursor_row + n).min(self.rows - 1);
|
self.cursor_row = (self.cursor_row + n).min(self.rows - 1);
|
||||||
|
log::trace!("CSI B: cursor down {} from row {} to {}", n, old_row, self.cursor_row);
|
||||||
}
|
}
|
||||||
// Cursor Forward
|
// Cursor Forward
|
||||||
'C' => {
|
'C' => {
|
||||||
let n = params.get(0, 1).max(1) as usize;
|
let n = params.get(0, 1).max(1) as usize;
|
||||||
|
let old_col = self.cursor_col;
|
||||||
self.cursor_col = (self.cursor_col + n).min(self.cols - 1);
|
self.cursor_col = (self.cursor_col + n).min(self.cols - 1);
|
||||||
|
log::trace!("CSI C: cursor forward {} from col {} to {}", n, old_col, self.cursor_col);
|
||||||
}
|
}
|
||||||
// Cursor Back
|
// Cursor Back
|
||||||
'D' => {
|
'D' => {
|
||||||
@@ -1641,7 +1652,9 @@ impl Handler for Terminal {
|
|||||||
// Cursor Horizontal Absolute (CHA)
|
// Cursor Horizontal Absolute (CHA)
|
||||||
'G' => {
|
'G' => {
|
||||||
let col = params.get(0, 1).max(1) as usize;
|
let col = params.get(0, 1).max(1) as usize;
|
||||||
|
let old_col = self.cursor_col;
|
||||||
self.cursor_col = (col - 1).min(self.cols - 1);
|
self.cursor_col = (col - 1).min(self.cols - 1);
|
||||||
|
log::trace!("CSI G: cursor to col {} (was {})", self.cursor_col, old_col);
|
||||||
}
|
}
|
||||||
// Cursor Position
|
// Cursor Position
|
||||||
'H' | 'f' => {
|
'H' | 'f' => {
|
||||||
|
|||||||
Reference in New Issue
Block a user