Compare commits

...

2 Commits

Author SHA1 Message Date
Zacharias-Brohn 32ce278743 terminal is fixed and im not happy about it 2026-04-10 00:29:13 +02:00
Zacharias-Brohn 5129612f9f terminal is fixed and im not happy about it 2026-04-09 23:49:32 +02:00
4 changed files with 247 additions and 88 deletions
+6 -1
View File
@@ -459,11 +459,16 @@ fn vs_cell_bg(
// For default background (type 0), use fully transparent so the window's // For default background (type 0), use fully transparent so the window's
// clear color (which has background_opacity applied) shows through. // clear color (which has background_opacity applied) shows through.
// UNLESS the grid params specify an opaque background (e.g. alternate screen).
// Only non-default backgrounds should be opaque. // Only non-default backgrounds should be opaque.
// But NOT if the cell is selected (selection always has white bg) // But NOT if the cell is selected (selection always has white bg)
let bg_type = cell.bg & 0xFFu; let bg_type = cell.bg & 0xFFu;
if bg_type == COLOR_TYPE_DEFAULT && !is_reverse && !is_selected { if bg_type == COLOR_TYPE_DEFAULT && !is_reverse && !is_selected {
bg.a = 0.0; if grid_params.background_opacity < 1.0 {
bg.a = 0.0;
} else {
bg.a = 1.0;
}
} }
// Calculate cursor color // Calculate cursor color
+124 -42
View File
@@ -879,7 +879,7 @@ fn remove_pid_file() {
/// - Last segment is bold /// - Last segment is bold
/// - Section has a dark gray background color (#282828) /// - Section has a dark gray background color (#282828)
/// - Section ends with powerline arrow transition /// - Section ends with powerline arrow transition
fn build_cwd_section(cwd: &str) -> StatuslineSection { fn build_cwd_section(cwd: &str, is_light: bool) -> StatuslineSection {
// Colors to cycle through (skip 0 and 1 which are often near-white in custom schemes) // Colors to cycle through (skip 0 and 1 which are often near-white in custom schemes)
const COLORS: [u8; 6] = [2, 3, 4, 5, 6, 7]; const COLORS: [u8; 6] = [2, 3, 4, 5, 6, 7];
@@ -903,7 +903,12 @@ fn build_cwd_section(cwd: &str) -> StatuslineSection {
if segments.is_empty() { if segments.is_empty() {
// Root directory // Root directory
components.push(StatuslineComponent::new(" \u{F07C} / ").fg(COLORS[0])); components.push(StatuslineComponent::new(" \u{F07C} / ").fg(COLORS[0]));
return StatuslineSection::with_rgb_bg(0x28, 0x28, 0x28) let (bg_r, bg_g, bg_b) = if is_light {
(0xE0, 0xE0, 0xE0)
} else {
(0x28, 0x28, 0x28)
};
return StatuslineSection::with_rgb_bg(bg_r, bg_g, bg_b)
.with_components(components); .with_components(components);
} }
@@ -940,8 +945,12 @@ fn build_cwd_section(cwd: &str) -> StatuslineSection {
// Add trailing space for padding before the powerline arrow // Add trailing space for padding before the powerline arrow
components.push(StatuslineComponent::new(" ")); components.push(StatuslineComponent::new(" "));
// Use dark gray (#282828) as section background let (bg_r, bg_g, bg_b) = if is_light {
StatuslineSection::with_rgb_bg(0x28, 0x28, 0x28).with_components(components) (0xE0, 0xE0, 0xE0)
} else {
(0x28, 0x28, 0x28)
};
StatuslineSection::with_rgb_bg(bg_r, bg_g, bg_b).with_components(components)
} }
/// Git repository status information. /// Git repository status information.
@@ -1101,7 +1110,7 @@ fn get_git_status(cwd: &str) -> Option<GitStatus> {
/// Build a statusline section for git status. /// Build a statusline section for git status.
/// Returns None if not in a git repository. /// Returns None if not in a git repository.
fn build_git_section(cwd: &str) -> Option<StatuslineSection> { fn build_git_section(cwd: &str, is_light: bool) -> Option<StatuslineSection> {
let status = get_git_status(cwd)?; let status = get_git_status(cwd)?;
// Determine foreground color based on state (matching oh-my-posh template) // Determine foreground color based on state (matching oh-my-posh template)
@@ -1111,13 +1120,29 @@ fn build_git_section(cwd: &str) -> Option<StatuslineSection> {
// 3. If both ahead and behind: #ff4500 (red-orange) // 3. If both ahead and behind: #ff4500 (red-orange)
// 4. If ahead or behind: #B388FF (purple) // 4. If ahead or behind: #B388FF (purple)
let fg_color: (u8, u8, u8) = if status.ahead > 0 && status.behind > 0 { let fg_color: (u8, u8, u8) = if status.ahead > 0 && status.behind > 0 {
(0xff, 0x45, 0x00) // #ff4500 - red-orange if is_light {
(0xCC, 0x37, 0x00)
} else {
(0xff, 0x45, 0x00)
} // red-orange (darker for light mode)
} else if status.ahead > 0 || status.behind > 0 { } else if status.ahead > 0 || status.behind > 0 {
(0xB3, 0x88, 0xFF) // #B388FF - purple if is_light {
(0x7B, 0x4C, 0xCC)
} else {
(0xB3, 0x88, 0xFF)
} // purple (darker for light mode)
} else if status.working_changed > 0 || status.staging_changed > 0 { } else if status.working_changed > 0 || status.staging_changed > 0 {
(0xFF, 0x92, 0x48) // #FF9248 - orange if is_light {
(0xC0, 0x60, 0x10)
} else {
(0xFF, 0x92, 0x48)
} // orange (darker for light mode)
} else { } else {
(0x0d, 0xa3, 0x00) // #0da300 - green if is_light {
(0x0A, 0x7D, 0x00)
} else {
(0x0d, 0xa3, 0x00)
} // green (darker for light mode)
}; };
let mut components = Vec::new(); let mut components = Vec::new();
@@ -1192,9 +1217,15 @@ fn build_git_section(cwd: &str) -> Option<StatuslineSection> {
.rgb_fg(fg_color.0, fg_color.1, fg_color.2), .rgb_fg(fg_color.0, fg_color.1, fg_color.2),
); );
// Background: #232323 // Background
let (bg_r, bg_g, bg_b) = if is_light {
(0xD0, 0xD0, 0xD0) // Light mode background
} else {
(0x23, 0x23, 0x23) // Dark mode background
};
Some( Some(
StatuslineSection::with_rgb_bg(0x23, 0x23, 0x23) StatuslineSection::with_rgb_bg(bg_r, bg_g, bg_b)
.with_components(components), .with_components(components),
) )
} }
@@ -1293,6 +1324,8 @@ struct App {
shutdown: Arc<AtomicBool>, shutdown: Arc<AtomicBool>,
/// Current mouse cursor position. /// Current mouse cursor position.
cursor_position: PhysicalPosition<f64>, cursor_position: PhysicalPosition<f64>,
/// Where the mouse was last pressed (for drag selection threshold).
mouse_down_pos: Option<PhysicalPosition<f64>>,
/// Frame counter for FPS logging. /// Frame counter for FPS logging.
frame_count: u64, frame_count: u64,
/// Last time we logged FPS. /// Last time we logged FPS.
@@ -1345,6 +1378,7 @@ impl App {
event_loop_proxy: None, event_loop_proxy: None,
shutdown: Arc::new(AtomicBool::new(false)), shutdown: Arc::new(AtomicBool::new(false)),
cursor_position: PhysicalPosition::new(0.0, 0.0), cursor_position: PhysicalPosition::new(0.0, 0.0),
mouse_down_pos: None,
frame_count: 0, frame_count: 0,
last_frame_log: std::time::Instant::now(), last_frame_log: std::time::Instant::now(),
should_create_window: false, should_create_window: false,
@@ -2019,8 +2053,12 @@ impl App {
if let Some(ref custom) = pane.custom_statusline { if let Some(ref custom) = pane.custom_statusline {
StatuslineContent::Raw(custom.clone()) StatuslineContent::Raw(custom.clone())
} else if let Some(cwd) = pane.pty.foreground_cwd() { } else if let Some(cwd) = pane.pty.foreground_cwd() {
let mut sections = vec![build_cwd_section(&cwd)]; let is_light = pane.terminal.palette.is_light();
if let Some(git_section) = build_git_section(&cwd) { let mut sections =
vec![build_cwd_section(&cwd, is_light)];
if let Some(git_section) =
build_git_section(&cwd, is_light)
{
sections.push(git_section); sections.push(git_section);
} }
StatuslineContent::Sections(sections) StatuslineContent::Sections(sections)
@@ -2536,8 +2574,16 @@ impl App {
} }
} }
text.push_str(line.trim_end()); let is_wrapped =
if content_row < end.row { row_cells.first().map(|c| c.wrapped).unwrap_or(false);
if is_wrapped {
text.push_str(&line);
} else {
text.push_str(line.trim_end());
}
if content_row < end.row && !is_wrapped {
text.push('\n'); text.push('\n');
} }
} }
@@ -2972,6 +3018,63 @@ impl ApplicationHandler<UserEvent> for App {
} }
} }
} }
} else if let Some(down_pos) = self.mouse_down_pos {
if !self.has_mouse_tracking() {
let dx = position.x - down_pos.x;
let dy = position.y - down_pos.y;
let distance_sq = dx * dx + dy * dy;
let cell_width = self
.renderer
.as_ref()
.map(|r| r.cell_metrics.cell_width as f64)
.unwrap_or(8.0);
let threshold = cell_width * 0.5;
if distance_sq > threshold * threshold {
// Dragged far enough, start selection
if let Some(renderer) = &self.renderer {
if let Some((start_col, start_screen_row)) =
renderer
.pixel_to_cell(down_pos.x, down_pos.y)
{
if let Some((end_col, end_screen_row)) =
renderer.pixel_to_cell(
position.x, position.y,
)
{
let scroll_offset =
self.get_scroll_offset() as isize;
let start_pos = CellPosition {
col: start_col,
row: start_screen_row as isize
- scroll_offset,
};
let end_pos = CellPosition {
col: end_col,
row: end_screen_row as isize
- scroll_offset,
};
if let Some(tab) = self.active_tab_mut()
{
if let Some(pane) =
tab.active_pane_mut()
{
pane.selection =
Some(Selection {
start: start_pos,
end: end_pos,
});
pane.is_selecting = true;
self.request_redraw();
}
}
}
}
}
}
}
} }
} }
@@ -3014,37 +3117,16 @@ impl ApplicationHandler<UserEvent> for App {
} else if button == MouseButton::Left { } else if button == MouseButton::Left {
match state { match state {
ElementState::Pressed => { ElementState::Pressed => {
if let Some(renderer) = &self.renderer { self.mouse_down_pos = Some(self.cursor_position);
if let Some((col, screen_row)) = renderer if let Some(tab) = self.active_tab_mut() {
.pixel_to_cell( if let Some(pane) = tab.active_pane_mut() {
self.cursor_position.x, pane.selection = None;
self.cursor_position.y, pane.is_selecting = false;
)
{
let scroll_offset =
self.get_scroll_offset();
let content_row = screen_row as isize
- scroll_offset as isize;
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(pane) =
tab.active_pane_mut()
{
pane.selection = Some(Selection {
start: pos,
end: pos,
});
pane.is_selecting = true;
}
}
} }
} }
} }
ElementState::Released => { ElementState::Released => {
self.mouse_down_pos = None;
let was_selecting = self let was_selecting = self
.active_pane() .active_pane()
.map(|p| p.is_selecting) .map(|p| p.is_selecting)
+69 -37
View File
@@ -13,7 +13,7 @@ use crate::gpu_types::{
EdgeGlowUniforms, QuadParams, StatuslineParams, EdgeGlowUniforms, QuadParams, StatuslineParams,
ATLAS_SIZE, MAX_ATLAS_LAYERS, ATLAS_BPP, MAX_EDGE_GLOWS, ATLAS_SIZE, MAX_ATLAS_LAYERS, ATLAS_BPP, MAX_EDGE_GLOWS,
COLOR_TYPE_DEFAULT, COLOR_TYPE_INDEXED, COLOR_TYPE_RGB, COLOR_TYPE_DEFAULT, COLOR_TYPE_INDEXED, COLOR_TYPE_RGB,
ATTR_BOLD, ATTR_ITALIC, ATTR_STRIKE, ATTR_BOLD, ATTR_ITALIC, ATTR_STRIKE, ATTR_REVERSE,
COLORED_GLYPH_FLAG, COLORED_GLYPH_FLAG,
CURSOR_SPRITE_BEAM, CURSOR_SPRITE_UNDERLINE, CURSOR_SPRITE_HOLLOW, CURSOR_SPRITE_BEAM, CURSOR_SPRITE_UNDERLINE, CURSOR_SPRITE_HOLLOW,
DECORATION_SPRITE_STRIKETHROUGH, DECORATION_SPRITE_UNDERLINE, DECORATION_SPRITE_DOUBLE_UNDERLINE, DECORATION_SPRITE_STRIKETHROUGH, DECORATION_SPRITE_UNDERLINE, DECORATION_SPRITE_DOUBLE_UNDERLINE,
@@ -1718,11 +1718,12 @@ impl Renderer {
/// Pack cell attributes into u32 format for GPU. /// Pack cell attributes into u32 format for GPU.
/// underline_style: 0=none, 1=single, 2=double, 3=curly, 4=dotted, 5=dashed /// underline_style: 0=none, 1=single, 2=double, 3=curly, 4=dotted, 5=dashed
#[inline] #[inline]
fn pack_attrs(bold: bool, italic: bool, underline_style: u8, strikethrough: bool) -> u32 { fn pack_attrs(bold: bool, italic: bool, underline_style: u8, strikethrough: bool, reverse: bool) -> u32 {
let mut attrs = (underline_style as u32) & 0x7; // 3 bits for decoration type let mut attrs = (underline_style as u32) & 0x7; // 3 bits for decoration type
if bold { attrs |= ATTR_BOLD; } if bold { attrs |= ATTR_BOLD; }
if italic { attrs |= ATTR_ITALIC; } if italic { attrs |= ATTR_ITALIC; }
if strikethrough { attrs |= ATTR_STRIKE; } if strikethrough { attrs |= ATTR_STRIKE; }
if reverse { attrs |= ATTR_REVERSE; }
attrs attrs
} }
@@ -1899,7 +1900,7 @@ impl Renderer {
bg: Self::pack_color(&cell.bg_color), bg: Self::pack_color(&cell.bg_color),
decoration_fg: 0, decoration_fg: 0,
sprite_idx: 0, // No glyph for continuation sprite_idx: 0, // No glyph for continuation
attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough), attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough, cell.reverse),
}; };
col += 1; col += 1;
continue; continue;
@@ -1972,7 +1973,7 @@ impl Renderer {
bg: Self::pack_color(&current_cell.bg_color), bg: Self::pack_color(&current_cell.bg_color),
decoration_fg: 0, decoration_fg: 0,
sprite_idx: final_sprite_idx, sprite_idx: final_sprite_idx,
attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough), attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough, cell.reverse),
}; };
} }
@@ -2023,7 +2024,7 @@ impl Renderer {
bg: Self::pack_color(&current_cell.bg_color), bg: Self::pack_color(&current_cell.bg_color),
decoration_fg: 0, decoration_fg: 0,
sprite_idx: sprite_idx | COLORED_GLYPH_FLAG, sprite_idx: sprite_idx | COLORED_GLYPH_FLAG,
attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough), attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough, cell.reverse),
}; };
} }
@@ -2052,7 +2053,7 @@ impl Renderer {
bg: Self::pack_color(&cell.bg_color), bg: Self::pack_color(&cell.bg_color),
decoration_fg: 0, decoration_fg: 0,
sprite_idx, sprite_idx,
attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough), attrs: Self::pack_attrs(cell.bold, cell.italic, cell.underline_style, cell.strikethrough, cell.reverse),
}; };
col += 1; col += 1;
} }
@@ -2405,14 +2406,19 @@ impl Renderer {
/// Parse ANSI escape sequences from raw statusline content. /// Parse ANSI escape sequences from raw statusline content.
/// Returns a vector of (char, fg_color, bg_color, bold) tuples. /// Returns a vector of (char, fg_color, bg_color, bold) tuples.
fn parse_ansi_statusline(content: &str) -> Vec<(char, StatuslineColor, StatuslineColor, bool)> { fn parse_ansi_statusline(content: &str, is_light: bool) -> Vec<(char, StatuslineColor, StatuslineColor, bool)> {
let mut result = Vec::new(); let mut result = Vec::new();
let chars: Vec<char> = content.chars().collect(); let chars: Vec<char> = content.chars().collect();
let mut i = 0; let mut i = 0;
// Current styling state // Current styling state
let mut fg = StatuslineColor::Default; let mut fg = StatuslineColor::Default;
let mut bg = StatuslineColor::Rgb(0x1a, 0x1a, 0x1a); // Default statusline background let default_bg_color = if is_light {
StatuslineColor::Rgb(0xD0, 0xD0, 0xD0)
} else {
StatuslineColor::Rgb(0x1a, 0x1a, 0x1a)
};
let mut bg = default_bg_color.clone(); // Default statusline background
let mut bold = false; let mut bold = false;
while i < chars.len() { while i < chars.len() {
@@ -2493,7 +2499,7 @@ impl Renderer {
} }
} }
} }
49 => bg = StatuslineColor::Rgb(0x1a, 0x1a, 0x1a), // Reset to default statusline bg 49 => bg = default_bg_color.clone(), // Reset to default statusline bg
90..=97 => fg = StatuslineColor::Indexed((code - 90 + 8) as u8), 90..=97 => fg = StatuslineColor::Indexed((code - 90 + 8) as u8),
100..=107 => bg = StatuslineColor::Indexed((code - 100 + 8) as u8), 100..=107 => bg = StatuslineColor::Indexed((code - 100 + 8) as u8),
_ => {} _ => {}
@@ -2527,7 +2533,7 @@ impl Renderer {
/// this is used to expand the middle gap to fill the full window width. /// this is used to expand the middle gap to fill the full window width.
/// ///
/// Returns the number of columns used. /// Returns the number of columns used.
fn update_statusline_cells(&mut self, content: &StatuslineContent, target_width: f32) -> usize { fn update_statusline_cells(&mut self, content: &StatuslineContent, target_width: f32, is_light: bool) -> usize {
self.statusline_gpu_cells.clear(); self.statusline_gpu_cells.clear();
// Calculate target columns based on window width // Calculate target columns based on window width
@@ -2539,14 +2545,19 @@ impl Renderer {
self.statusline_max_cols self.statusline_max_cols
}; };
// Default background color for statusline (dark gray) // Default background color for statusline
let default_bg = Self::pack_statusline_color(StatuslineColor::Rgb(0x1a, 0x1a, 0x1a)); let default_bg_color = if is_light {
StatuslineColor::Rgb(0xD0, 0xD0, 0xD0)
} else {
StatuslineColor::Rgb(0x1a, 0x1a, 0x1a)
};
let default_bg = Self::pack_statusline_color(default_bg_color);
let _ = default_bg; // Silence unused warning - used by Sections path let _ = default_bg; // Silence unused warning - used by Sections path
match content { match content {
StatuslineContent::Raw(ansi_content) => { StatuslineContent::Raw(ansi_content) => {
// Parse ANSI escape sequences to extract colors and text // Parse ANSI escape sequences to extract colors and text
let parsed = Self::parse_ansi_statusline(ansi_content); let parsed = Self::parse_ansi_statusline(ansi_content, is_light);
// Find the middle gap (largest consecutive run of spaces) // Find the middle gap (largest consecutive run of spaces)
// and expand it to fill the target width // and expand it to fill the target width
@@ -2596,7 +2607,7 @@ impl Renderer {
let gap_bg = if best_gap_len > 0 && best_gap_start < parsed.len() { let gap_bg = if best_gap_len > 0 && best_gap_start < parsed.len() {
parsed[best_gap_start].2 parsed[best_gap_start].2
} else { } else {
StatuslineColor::Rgb(0x1a, 0x1a, 0x1a) default_bg_color.clone()
}; };
// The position right before right-hand content starts (end of gap) // The position right before right-hand content starts (end of gap)
@@ -2628,7 +2639,7 @@ impl Renderer {
let fg = Self::pack_statusline_color(*fg_color); let fg = Self::pack_statusline_color(*fg_color);
let bg = Self::pack_statusline_color(*bg_color); let bg = Self::pack_statusline_color(*bg_color);
let style = if *bold { FontStyle::Bold } else { FontStyle::Regular }; let style = if *bold { FontStyle::Bold } else { FontStyle::Regular };
let attrs = Self::pack_attrs(*bold, false, 0, false); let attrs = Self::pack_attrs(*bold, false, 0, false, false);
let (sprite_idx, is_colored) = if *c == ' ' || *c == '\0' { let (sprite_idx, is_colored) = if *c == ' ' || *c == '\0' {
(0, false) (0, false)
@@ -2677,7 +2688,7 @@ impl Renderer {
let fg = Self::pack_statusline_color(fg_color); let fg = Self::pack_statusline_color(fg_color);
let bg = Self::pack_statusline_color(bg_color); let bg = Self::pack_statusline_color(bg_color);
let style = if bold { FontStyle::Bold } else { FontStyle::Regular }; let style = if bold { FontStyle::Bold } else { FontStyle::Regular };
let attrs = Self::pack_attrs(bold, false, 0, false); let attrs = Self::pack_attrs(bold, false, 0, false, false);
let (sprite_idx, is_colored) = if c == ' ' || c == '\0' { let (sprite_idx, is_colored) = if c == ' ' || c == '\0' {
(0, false) (0, false)
@@ -2715,7 +2726,7 @@ impl Renderer {
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 };
let attrs = Self::pack_attrs(component.bold, false, 0, false); let attrs = Self::pack_attrs(component.bold, false, 0, false, false);
// Process characters with lookahead for multi-cell symbols // Process characters with lookahead for multi-cell symbols
let chars: Vec<char> = component.text.chars().collect(); let chars: Vec<char> = component.text.chars().collect();
@@ -2844,7 +2855,7 @@ impl Renderer {
// Fill remaining width with default background cells // Fill remaining width with default background cells
// This ensures the statusline covers the entire window width // This ensures the statusline covers the entire window width
let default_bg_packed = Self::pack_statusline_color(StatuslineColor::Default); let default_bg_packed = default_bg;
while self.statusline_gpu_cells.len() < target_cols && self.statusline_gpu_cells.len() < self.statusline_max_cols { while self.statusline_gpu_cells.len() < target_cols && self.statusline_gpu_cells.len() < self.statusline_max_cols {
self.statusline_gpu_cells.push(GPUCell { self.statusline_gpu_cells.push(GPUCell {
fg: 0, fg: 0,
@@ -4330,10 +4341,17 @@ impl Renderer {
TabBarPosition::Hidden => unreachable!(), TabBarPosition::Hidden => unreachable!(),
}; };
// Use same color as statusline: 0x1a1a1a (26, 26, 26) in sRGB let is_light = self.palette.is_light();
// Pre-computed linear RGB value for srgb_to_linear(26/255) ≈ 0.00972 let tab_bar_bg = if is_light {
const TAB_BAR_BG_LINEAR: f32 = 0.00972; // Light mode statusline bg is approx 0xD0, linear is ~0.63076
let tab_bar_bg = [TAB_BAR_BG_LINEAR, TAB_BAR_BG_LINEAR, TAB_BAR_BG_LINEAR, 1.0]; const TAB_BAR_BG_LINEAR_LIGHT: f32 = 0.63076;
[TAB_BAR_BG_LINEAR_LIGHT, TAB_BAR_BG_LINEAR_LIGHT, TAB_BAR_BG_LINEAR_LIGHT, 1.0]
} else {
// Use same color as statusline: 0x1a1a1a (26, 26, 26) in sRGB
// Pre-computed linear RGB value for srgb_to_linear(26/255) ≈ 0.00972
const TAB_BAR_BG_LINEAR_DARK: f32 = 0.00972;
[TAB_BAR_BG_LINEAR_DARK, TAB_BAR_BG_LINEAR_DARK, TAB_BAR_BG_LINEAR_DARK, 1.0]
};
// Draw tab bar background // Draw tab bar background
log::debug!("render_panes: drawing tab bar at y={}, height={}, num_tabs={}, quads_before={}", log::debug!("render_panes: drawing tab bar at y={}, height={}, num_tabs={}, quads_before={}",
@@ -4353,23 +4371,23 @@ 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 // Active tab: brightest - matches terminal background or slightly brighter
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 let boost = if is_light { 0.0_f32 } else { 50.0_f32 };
[ [
Self::srgb_to_linear((r as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((r as f32 + boost).clamp(0.0, 255.0) / 255.0),
Self::srgb_to_linear((g as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((g as f32 + boost).clamp(0.0, 255.0) / 255.0),
Self::srgb_to_linear((b as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((b as f32 + boost).clamp(0.0, 255.0) / 255.0),
1.0, 1.0,
] ]
} else { } else {
// Inactive tab: slightly brighter than tab bar background // Inactive tab: between tab bar background and active tab
let [r, g, b] = self.palette.default_bg; let [r, g, b] = self.palette.default_bg;
let boost = 30.0_f32; let boost = if is_light { -30.0_f32 } else { 30.0_f32 };
[ [
Self::srgb_to_linear((r as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((r as f32 + boost).clamp(0.0, 255.0) / 255.0),
Self::srgb_to_linear((g as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((g as f32 + boost).clamp(0.0, 255.0) / 255.0),
Self::srgb_to_linear((b as f32 + boost).min(255.0) / 255.0), Self::srgb_to_linear((b as f32 + boost).clamp(0.0, 255.0) / 255.0),
1.0, 1.0,
] ]
}; };
@@ -4650,7 +4668,11 @@ impl Renderer {
CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline => 1, CursorShape::BlinkingUnderline | CursorShape::SteadyUnderline => 1,
CursorShape::BlinkingBar | CursorShape::SteadyBar => 2, CursorShape::BlinkingBar | CursorShape::SteadyBar => 2,
}, },
background_opacity: self.background_opacity, background_opacity: if terminal.using_alternate_screen {
1.0
} else {
self.background_opacity
},
selection_start_col: sel_start_col, selection_start_col: sel_start_col,
selection_start_row: sel_start_row, selection_start_row: sel_start_row,
selection_end_col: sel_end_col, selection_end_col: sel_end_col,
@@ -4766,9 +4788,10 @@ impl Renderer {
// ═══════════════════════════════════════════════════════════════════ // ═══════════════════════════════════════════════════════════════════
let statusline_cols = { let statusline_cols = {
let statusline_y = self.statusline_y(); let statusline_y = self.statusline_y();
let is_light = self.palette.is_light();
// Update statusline GPU cells from content, passing window width for gap expansion // Update statusline GPU cells from content, passing window width for gap expansion
let cols = self.update_statusline_cells(statusline_content, width); let cols = self.update_statusline_cells(statusline_content, width, is_light);
if cols > 0 { if cols > 0 {
// Upload statusline cells to GPU // Upload statusline cells to GPU
@@ -5008,10 +5031,19 @@ impl Renderer {
{ {
let [bg_r, bg_g, bg_b] = self.palette.default_bg; let [bg_r, bg_g, bg_b] = self.palette.default_bg;
let bg_r_linear = Self::srgb_to_linear(bg_r as f32 / 255.0) as f64; let mut bg_r_linear = Self::srgb_to_linear(bg_r as f32 / 255.0) as f64;
let bg_g_linear = Self::srgb_to_linear(bg_g as f32 / 255.0) as f64; let mut bg_g_linear = Self::srgb_to_linear(bg_g as f32 / 255.0) as f64;
let bg_b_linear = Self::srgb_to_linear(bg_b as f32 / 255.0) as f64; let mut bg_b_linear = Self::srgb_to_linear(bg_b as f32 / 255.0) as f64;
let bg_alpha = self.background_opacity as f64; let bg_alpha = self.background_opacity as f64;
// If the compositor expects premultiplied alpha, we must premultiply the clear color.
// Otherwise, light backgrounds with opacity will look fully opaque or super-luminous.
if self.surface_config.alpha_mode == wgpu::CompositeAlphaMode::PreMultiplied {
bg_r_linear *= bg_alpha;
bg_g_linear *= bg_alpha;
bg_b_linear *= bg_alpha;
}
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"), label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+48 -8
View File
@@ -38,6 +38,7 @@ pub struct Cell {
pub bg_color: Color, pub bg_color: Color,
pub bold: bool, pub bold: bool,
pub italic: bool, pub italic: bool,
pub reverse: bool,
/// Underline style: 0=none, 1=single, 2=double, 3=curly, 4=dotted, 5=dashed /// Underline style: 0=none, 1=single, 2=double, 3=curly, 4=dotted, 5=dashed
pub underline_style: u8, pub underline_style: u8,
/// Strikethrough decoration /// Strikethrough decoration
@@ -45,6 +46,8 @@ pub struct Cell {
/// If true, this cell is the continuation of a wide (double-width) character. /// If true, this cell is the continuation of a wide (double-width) character.
/// The actual character is stored in the previous cell. /// The actual character is stored in the previous cell.
pub wide_continuation: bool, pub wide_continuation: bool,
/// Indicates if the line wrapped after this cell.
pub wrapped: bool,
} }
impl Default for Cell { impl Default for Cell {
@@ -55,9 +58,11 @@ impl Default for Cell {
bg_color: Color::Default, bg_color: Color::Default,
bold: false, bold: false,
italic: false, italic: false,
reverse: false,
underline_style: 0, underline_style: 0,
strikethrough: false, strikethrough: false,
wide_continuation: false, wide_continuation: false,
wrapped: false,
} }
} }
} }
@@ -181,6 +186,15 @@ impl Default for ColorPalette {
} }
impl ColorPalette { impl ColorPalette {
/// Return whether this palette is considered "light" based on default background luminance.
pub fn is_light(&self) -> bool {
let [r, g, b] = self.default_bg;
// Standard perceived luminance calculation
let luminance =
0.299 * (r as f32) + 0.587 * (g as f32) + 0.114 * (b as f32);
luminance > 128.0
}
/// Parse a color specification like "#RRGGBB" or "rgb:RR/GG/BB". /// Parse a color specification like "#RRGGBB" or "rgb:RR/GG/BB".
pub fn parse_color_spec(spec: &str) -> Option<[u8; 3]> { pub fn parse_color_spec(spec: &str) -> Option<[u8; 3]> {
let spec = spec.trim(); let spec = spec.trim();
@@ -269,6 +283,7 @@ struct SavedCursor {
strikethrough: bool, strikethrough: bool,
origin_mode: bool, origin_mode: bool,
auto_wrap: bool, auto_wrap: bool,
reverse: bool,
} }
/// Alternate screen buffer state. /// Alternate screen buffer state.
@@ -509,6 +524,8 @@ pub struct Terminal {
pub current_underline_style: u8, pub current_underline_style: u8,
/// Current strikethrough state. /// Current strikethrough state.
pub current_strikethrough: bool, pub current_strikethrough: bool,
/// Current reverse video state.
pub current_reverse: bool,
/// Whether the terminal content has changed. /// Whether the terminal content has changed.
pub dirty: bool, pub dirty: bool,
/// Bitmap of dirty lines - bit N is set if line N needs redrawing. /// Bitmap of dirty lines - bit N is set if line N needs redrawing.
@@ -594,6 +611,7 @@ impl Terminal {
current_italic: false, current_italic: false,
current_underline_style: 0, current_underline_style: 0,
current_strikethrough: false, current_strikethrough: false,
current_reverse: false,
dirty: true, dirty: true,
dirty_lines: [!0u64; 4], // All lines dirty initially dirty_lines: [!0u64; 4], // All lines dirty initially
scroll_top: 0, scroll_top: 0,
@@ -697,9 +715,11 @@ impl Terminal {
bg_color: self.current_bg, bg_color: self.current_bg,
bold: self.current_bold, bold: self.current_bold,
italic: self.current_italic, italic: self.current_italic,
reverse: self.current_reverse,
underline_style: self.current_underline_style, underline_style: self.current_underline_style,
strikethrough: self.current_strikethrough, strikethrough: self.current_strikethrough,
wide_continuation, wide_continuation,
wrapped: false,
} }
} }
@@ -741,9 +761,11 @@ impl Terminal {
bg_color: self.current_bg, bg_color: self.current_bg,
bold: false, bold: false,
italic: false, italic: false,
reverse: false,
underline_style: 0, underline_style: 0,
strikethrough: false, strikethrough: false,
wide_continuation: false, wide_continuation: false,
wrapped: false,
} }
} }
@@ -1459,6 +1481,9 @@ impl Handler for Terminal {
// Handle wrap // Handle wrap
if self.cursor_col >= self.cols { if self.cursor_col >= self.cols {
if self.auto_wrap { if self.auto_wrap {
if self.cols > 0 {
self.grid[grid_row][0].wrapped = true;
}
self.cursor_col = 0; self.cursor_col = 0;
self.advance_row(); self.advance_row();
cached_row = self.cursor_row; cached_row = self.cursor_row;
@@ -1970,6 +1995,10 @@ impl Handler for Terminal {
// Handle wrap // Handle wrap
if self.cursor_col >= self.cols { if self.cursor_col >= self.cols {
if self.auto_wrap { if self.auto_wrap {
let gr = self.line_map[self.cursor_row];
if self.cols > 0 {
self.grid[gr][0].wrapped = true;
}
self.cursor_col = 0; self.cursor_col = 0;
self.advance_row(); self.advance_row();
self.mark_line_dirty(self.cursor_row); self.mark_line_dirty(self.cursor_row);
@@ -2167,15 +2196,17 @@ impl Handler for Terminal {
italic: self.current_italic, italic: self.current_italic,
underline_style: self.current_underline_style, underline_style: self.current_underline_style,
strikethrough: self.current_strikethrough, strikethrough: self.current_strikethrough,
reverse: self.current_reverse,
origin_mode: self.origin_mode, origin_mode: self.origin_mode,
auto_wrap: self.auto_wrap, auto_wrap: self.auto_wrap,
}; };
log::debug!( log::debug!(
"ESC 7: Cursor saved at ({}, {}), origin_mode={}, auto_wrap={}", "ESC 7: Cursor saved at ({}, {}), origin_mode={}, auto_wrap={}, reverse={}",
self.cursor_col, self.cursor_col,
self.cursor_row, self.cursor_row,
self.origin_mode, self.origin_mode,
self.auto_wrap self.auto_wrap,
self.current_reverse
); );
} }
@@ -2190,14 +2221,16 @@ impl Handler for Terminal {
self.current_italic = self.saved_cursor.italic; self.current_italic = self.saved_cursor.italic;
self.current_underline_style = self.saved_cursor.underline_style; self.current_underline_style = self.saved_cursor.underline_style;
self.current_strikethrough = self.saved_cursor.strikethrough; self.current_strikethrough = self.saved_cursor.strikethrough;
self.current_reverse = self.saved_cursor.reverse;
self.origin_mode = self.saved_cursor.origin_mode; self.origin_mode = self.saved_cursor.origin_mode;
self.auto_wrap = self.saved_cursor.auto_wrap; self.auto_wrap = self.saved_cursor.auto_wrap;
log::debug!( log::debug!(
"ESC 8: Cursor restored to ({}, {}), origin_mode={}, auto_wrap={}", "ESC 8: Cursor restored to ({}, {}), origin_mode={}, auto_wrap={}, reverse={}",
self.cursor_col, self.cursor_col,
self.cursor_row, self.cursor_row,
self.origin_mode, self.origin_mode,
self.auto_wrap self.auto_wrap,
self.current_reverse
); );
} }
@@ -2285,9 +2318,11 @@ impl Handler for Terminal {
bg_color: Color::Default, bg_color: Color::Default,
bold: false, bold: false,
italic: false, italic: false,
reverse: false,
underline_style: 0, underline_style: 0,
strikethrough: false, strikethrough: false,
wide_continuation: false, wide_continuation: false,
wrapped: false,
}; };
} }
self.mark_line_dirty(visual_row); self.mark_line_dirty(visual_row);
@@ -2324,6 +2359,10 @@ impl Terminal {
// Check if we need to wrap before printing // Check if we need to wrap before printing
if self.cursor_col >= self.cols { if self.cursor_col >= self.cols {
if self.auto_wrap { if self.auto_wrap {
let grid_row = self.line_map[self.cursor_row];
if self.cols > 0 {
self.grid[grid_row][0].wrapped = true;
}
self.cursor_col = 0; self.cursor_col = 0;
self.advance_row(); self.advance_row();
} else { } else {
@@ -2338,6 +2377,7 @@ impl Terminal {
// Write a space in the last column and wrap // Write a space in the last column and wrap
let grid_row = self.line_map[self.cursor_row]; let grid_row = self.line_map[self.cursor_row];
self.grid[grid_row][self.cursor_col] = Cell::default(); self.grid[grid_row][self.cursor_col] = Cell::default();
self.grid[grid_row][0].wrapped = true;
self.cursor_col = 0; self.cursor_col = 0;
self.advance_row(); self.advance_row();
} else { } else {
@@ -2460,15 +2500,14 @@ impl Terminal {
self.current_underline_style = 1; self.current_underline_style = 1;
} }
} }
7 => std::mem::swap(&mut self.current_fg, &mut self.current_bg), 7 => self.current_reverse = true,
9 => self.current_strikethrough = true, 9 => self.current_strikethrough = true,
21 => self.current_underline_style = 2, // Double underline 21 => self.current_underline_style = 2, // Double underline
22 => self.current_bold = false, 22 => self.current_bold = false,
23 => self.current_italic = false, 23 => self.current_italic = false,
24 => self.current_underline_style = 0, 24 => self.current_underline_style = 0,
27 => { 27 => self.current_reverse = false,
std::mem::swap(&mut self.current_fg, &mut self.current_bg) 28 => self.current_reverse = false,
}
29 => self.current_strikethrough = false, 29 => self.current_strikethrough = false,
// Standard foreground colors (30-37) // Standard foreground colors (30-37)
30..=37 => self.current_fg = Color::Indexed((code - 30) as u8), 30..=37 => self.current_fg = Color::Indexed((code - 30) as u8),
@@ -2517,6 +2556,7 @@ impl Terminal {
self.current_italic = false; self.current_italic = false;
self.current_underline_style = 0; self.current_underline_style = 0;
self.current_strikethrough = false; self.current_strikethrough = false;
self.current_reverse = false;
} }
/// Handle Kitty keyboard protocol CSI sequences. /// Handle Kitty keyboard protocol CSI sequences.