diff --git a/kitty/screen.c b/kitty/screen.c index e13a52573..e9ac71fdd 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -3439,23 +3439,33 @@ effective_cell_edge_color(char_type ch, color_type fg, color_type bg, bool is_le bool -get_line_edge_colors(Screen *self, color_type *left, color_type *right) { - // Return the color at the left and right edges of the line with the cursor on it - Line *line = range_line_(self, self->cursor->y); +get_line_edge_colors_at_row(Screen *self, index_type y, color_type *left, color_type *right, bool *left_is_default, bool *right_is_default) { + // Return the color at the left and right edges of the specified row. + // Any of the output pointers may be NULL if that value is not needed. + Line *line = range_line_(self, y); if (!line) return false; color_type left_cell_fg = OPT(foreground), left_cell_bg = OPT(background), right_cell_bg = OPT(background), right_cell_fg = OPT(foreground); index_type cell_color_x = 0; char_type left_char = line_get_char(line, cell_color_x); bool reversed = false; colors_for_cell(line, self->color_profile, &cell_color_x, &left_cell_fg, &left_cell_bg, &reversed); + if (left_is_default) *left_is_default = (line->gpu_cells[cell_color_x].bg & 0xff) == 0; + if (left) *left = effective_cell_edge_color(left_char, left_cell_fg, left_cell_bg, true); if (line->xnum > 0) cell_color_x = line->xnum - 1; char_type right_char = line_get_char(line, cell_color_x); + reversed = false; colors_for_cell(line, self->color_profile, &cell_color_x, &right_cell_fg, &right_cell_bg, &reversed); - *left = effective_cell_edge_color(left_char, left_cell_fg, left_cell_bg, true); - *right = effective_cell_edge_color(right_char, right_cell_fg, right_cell_bg, false); + if (right_is_default) *right_is_default = (line->gpu_cells[cell_color_x].bg & 0xff) == 0; + if (right) *right = effective_cell_edge_color(right_char, right_cell_fg, right_cell_bg, false); return true; } +bool +get_line_edge_colors(Screen *self, color_type *left, color_type *right) { + // Return the color at the left and right edges of the line with the cursor on it + return get_line_edge_colors_at_row(self, self->cursor->y, left, right, NULL, NULL); +} + static void update_line_data(Line *line, unsigned int dest_y, uint8_t *data) { diff --git a/kitty/screen.h b/kitty/screen.h index 56903d122..6124fedf1 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -332,6 +332,7 @@ bool screen_prompt_supports_click_events(const Screen *, bool *is_relative); bool screen_fake_move_cursor_to_position(Screen *, index_type x, index_type y); bool screen_send_signal_for_key(Screen *, char key); bool get_line_edge_colors(Screen *self, color_type *left, color_type *right); +bool get_line_edge_colors_at_row(Screen *self, index_type y, color_type *left, color_type *right, bool *left_is_default, bool *right_is_default); bool parse_sgr(Screen *screen, const uint8_t *buf, unsigned int num, const char *report_name, bool is_deccara); bool screen_pause_rendering(Screen *self, bool pause, int for_in_ms); void screen_check_pause_rendering(Screen *self, monotonic_t now); diff --git a/kitty/state.c b/kitty/state.c index d0bdaf732..fba0a0599 100644 --- a/kitty/state.c +++ b/kitty/state.c @@ -1404,13 +1404,39 @@ PYWRAP1(patch_global_colors) { PYWRAP1(update_tab_bar_edge_colors) { id_type os_window_id; - PA("K", &os_window_id); + int is_vertical = 0; + PA("K|i", &os_window_id, &is_vertical); WITH_OS_WINDOW(os_window_id) - if (os_window->tab_bar_render_data.screen) { - if (get_line_edge_colors(os_window->tab_bar_render_data.screen, &os_window->tab_bar_edge_color.left, &os_window->tab_bar_edge_color.right)) { Py_RETURN_TRUE; } + Screen *screen = os_window->tab_bar_render_data.screen; + if (screen) { + bool left_is_default = true, right_is_default = true; + bool ok; + if (!is_vertical) { + ok = get_line_edge_colors_at_row( + screen, screen->cursor->y, + &os_window->tab_bar_edge_color.left, + &os_window->tab_bar_edge_color.right, + &left_is_default, &right_is_default); + } else { + color_type top_color = 0, bottom_color = 0; + bool top_is_default = true, bottom_is_default = true; + ok = get_line_edge_colors_at_row(screen, 0, &top_color, NULL, &top_is_default, NULL) && + get_line_edge_colors_at_row(screen, screen->lines - 1, &bottom_color, NULL, &bottom_is_default, NULL); + if (ok) { + os_window->tab_bar_edge_color.left = top_color; + os_window->tab_bar_edge_color.right = bottom_color; + left_is_default = top_is_default; + right_is_default = bottom_is_default; + } + } + if (ok) { + return Py_BuildValue("OO", + left_is_default ? Py_True : Py_False, + right_is_default ? Py_True : Py_False); + } } END_WITH_OS_WINDOW - Py_RETURN_FALSE; + Py_RETURN_NONE; } static PyObject* diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index b2e76a8ef..1d15a3876 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -619,6 +619,9 @@ class TabBar: self.blank_rects: tuple[Border, ...] = () self.tab_extents: Sequence[TabExtent] = () self.laid_out_once = False + self.left_edge_is_default = True + self.right_edge_is_default = True + self._last_viewport: tuple[Region, Region, int, int] | None = None self.apply_options() def apply_options(self) -> None: @@ -753,18 +756,23 @@ class TabBar: blank_rects.append(Border(0, tab_bar.bottom, vw, central.top, bg)) g = self.window_geometry if self.is_vertical: + if opts.tab_bar_margin_color is None: + top_bg = bg if self.left_edge_is_default else BorderColor.tab_bar_left_edge_color + bottom_bg = bg if self.right_edge_is_default else BorderColor.tab_bar_right_edge_color + else: + top_bg = bottom_bg = bg if g.left > tab_bar.left: blank_rects.append(Border(tab_bar.left, g.top, g.left, g.bottom, bg)) if g.right < tab_bar.right: blank_rects.append(Border(g.right, g.top, tab_bar.right, g.bottom, bg)) if g.top > tab_bar.top: - blank_rects.append(Border(g.left, tab_bar.top, g.right, g.top, bg)) + blank_rects.append(Border(g.left, tab_bar.top, g.right, g.top, top_bg)) if g.bottom < tab_bar.bottom: - blank_rects.append(Border(g.left, g.bottom, g.right, tab_bar.bottom, bg)) + blank_rects.append(Border(g.left, g.bottom, g.right, tab_bar.bottom, bottom_bg)) else: if opts.tab_bar_margin_color is None: - left_bg = BorderColor.tab_bar_left_edge_color - right_bg = BorderColor.tab_bar_right_edge_color + left_bg = bg if self.left_edge_is_default else BorderColor.tab_bar_left_edge_color + right_bg = bg if self.right_edge_is_default else BorderColor.tab_bar_right_edge_color else: left_bg = right_bg = bg if g.left > tab_bar.left: @@ -811,15 +819,31 @@ class TabBar: self.window_geometry = g = WindowGeometry( left_margin, tab_bar.top, left_margin + cell_area_width, tab_bar.bottom, s.columns, s.lines) self.laid_out_once = True + self._last_viewport = (central, tab_bar, vw, vh) self.update_blank_rects(central, tab_bar, vw, vh) set_tab_bar_render_data(self.os_window_id, self.screen, *g[:4]) - def update(self, data: Sequence[TabBarData]) -> None: + def _update_edge_defaults(self, is_vertical: bool) -> bool: + """Call update_tab_bar_edge_colors, update cached is-default flags. + Returns True when the flags changed and blank_rects need rebuilding.""" + result = update_tab_bar_edge_colors(self.os_window_id, int(is_vertical)) + if result is None: + return False + left_is_default, right_is_default = result + if left_is_default != self.left_edge_is_default or right_is_default != self.right_edge_is_default: + self.left_edge_is_default = left_is_default + self.right_edge_is_default = right_is_default + if self._last_viewport is not None: + self.update_blank_rects(*self._last_viewport) + return True + return False + + def update(self, data: Sequence[TabBarData]) -> bool: if not self.laid_out_once: - return + return False if self.is_vertical: - self.update_vertical(data) - return + return self.update_vertical(data) + s = self.screen last_tab = data[-1] if data else None ed = ExtraData() @@ -884,16 +908,16 @@ class TabBar: self.tab_extents = cr s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab self.align() - update_tab_bar_edge_colors(self.os_window_id) + return self._update_edge_defaults(False) - def update_vertical(self, data: Sequence[TabBarData]) -> None: + def update_vertical(self, data: Sequence[TabBarData]) -> bool: s = self.screen self.last_laid_out_tabs = data self.tab_extents = () s.cursor.x = s.cursor.y = 0 s.erase_in_display(2, False) if not data: - return + return self._update_edge_defaults(True) max_tab_length = max(1, s.columns - 1) tab_line_height = max(1, min(MAX_VERTICAL_TAB_LINES, s.lines // max(1, len(data)))) rows_to_draw = min(len(data), max(1, s.lines // tab_line_height)) @@ -926,6 +950,7 @@ class TabBar: s.cursor.fg = as_rgb(0xff0000) s.draw('…') self.tab_extents = tuple(cr) + return self._update_edge_defaults(True) def align_with_factor(self, factor: int = 1) -> None: if not self.tab_extents: diff --git a/kitty/tabs.py b/kitty/tabs.py index 97e741799..b42c3cdf1 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -1330,7 +1330,9 @@ class TabManager: # {{{ watcher(boss, w, data) def update_tab_bar_data(self) -> None: - self.tab_bar.update(self.tab_bar_data) + if self.tab_bar.update(self.tab_bar_data): + for tab in self.tabs: + tab.relayout_borders() def title_changed(self, tab: Tab) -> None: self.mark_tab_bar_dirty()