From 53ae5a4f8df0374153358f7699404346dab28e67 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 6 Jun 2018 13:27:15 +0530 Subject: [PATCH] A new tab bar style --- docs/changelog.rst | 11 +++++--- kitty/conf/utils.py | 12 +++++++++ kitty/config_data.py | 24 ++++++++++++++--- kitty/rgb.py | 12 +++++++++ kitty/tab_bar.py | 61 ++++++++++++++++++++++++++++++++++++-------- kitty/tabs.py | 2 +- 6 files changed, 104 insertions(+), 18 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index a6d689b93..52ed69073 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,18 +6,23 @@ Changelog 0.11.0 [future] ------------------------------ +- A new tab bar style "fade" in which each tabs edges fade into the background. + See :opt:`tab_bar_style` and :opt:`tab_fade` for details. The old look can be + restored by setting tab_bar_style to "separator". + - :doc:`Pre-compiled binaries ` with all bundled dependencies for Linux (:iss:`595`) -- A :doc:`new kitten ` to create dock panels on X11 desktops showing the output from - arbitrary terminal programs. +- A :doc:`new kitten ` to create dock panels on X11 desktops + showing the output from arbitrary terminal programs. - Reduce data sent to the GPU per render by 30% (:commit:`8dea5b3`) - Implement changing the font size for individual top level (OS) windows (:iss:`408`) -- ssh kitten: Support all SSH options. It can now be aliased directly to ssh for convenience. (:pull:`591`) +- ssh kitten: Support all SSH options. It can now be aliased directly to ssh + for convenience. (:pull:`591`) - icat kitten: Add :option:`kitty +kitten icat --print-window-size` to easily detect the window size in pixels from scripting languages (:iss:`581`) diff --git a/kitty/conf/utils.py b/kitty/conf/utils.py index 21e39fe96..0bec1ee95 100644 --- a/kitty/conf/utils.py +++ b/kitty/conf/utils.py @@ -43,6 +43,18 @@ def python_string(text): return ast.literal_eval("'''" + text.replace("'''", "'\\''") + "'''") +def choices(*choices): + defval = choices[0] + choices = frozenset(choices) + + def choice(x): + x = x.lower() + if x not in choices: + x = defval + return x + return choice + + def parse_line(line, type_map, special_handling, ans, all_keys, base_path_for_includes): line = line.strip() if not line or line.startswith('#'): diff --git a/kitty/config_data.py b/kitty/config_data.py index ec8ce1023..51167e05d 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -8,14 +8,13 @@ from gettext import gettext as _ from . import fast_data_types as defines from .conf.definition import option_func from .conf.utils import ( - positive_float, positive_int, to_cmdline, to_color, unit_float + choices, positive_float, positive_int, to_cmdline, to_color, unit_float ) from .fast_data_types import CURSOR_BEAM, CURSOR_BLOCK, CURSOR_UNDERLINE from .layout import all_layouts from .rgb import color_as_int, color_as_sharp, color_from_int from .utils import log_error - MINIMUM_FONT_SIZE = 4 @@ -490,8 +489,27 @@ Which edge to show the tab bar on, top or bottom''')) o('tab_bar_margin_width', 0.0, option_type=positive_float, long_text=_(''' The margin to the left and right of the tab bar (in pts)''')) +o('tab_bar_style', 'fade', option_type=choices('fade', 'separator'), long_text=_(''' +The tab bar style, can be one of: :code:`fade` or :code:`separator`. In the fade style, +each tab's edges fade into the background color, in the separator style, tabs are +separated by a configurable separator. +''')) + + +def tab_fade(x): + return tuple(map(unit_float, x.split())) + + +o('tab_fade', '0.25 0.5 0.75 1', option_type=tab_fade, long_text=_(''' +Control how each tab fades into the background when using :code:`fade` for the +:opt:`tab_bar_style`. Each number is an alpha (between zero and one) that controls +how much the corresponding cell fades into the background, with zero being no fade +and one being full fade. You can change the number of cells used by adding/removing +entries to this list. +''')) + o('tab_separator', '"{}"'.format(default_tab_separator), option_type=tab_separator, long_text=_(''' -The separator between tabs in the tab bar''')) +The separator between tabs in the tab bar when using :code:`separator` as the :opt:`tab_bar_style`.''')) o('active_tab_foreground', '#000', option_type=to_color, long_text=_(''' Tab bar colors and styles''')) diff --git a/kitty/rgb.py b/kitty/rgb.py index 95598b97d..051502127 100644 --- a/kitty/rgb.py +++ b/kitty/rgb.py @@ -8,6 +8,18 @@ from collections import namedtuple Color = namedtuple('Color', 'red green blue') +def alpha_blend_channel(top_color, bottom_color, alpha): + return int(alpha * top_color + (1 - alpha) * bottom_color) + + +def alpha_blend(top_color, bottom_color, alpha): + return Color( + alpha_blend_channel(top_color.red, bottom_color.red, alpha), + alpha_blend_channel(top_color.green, bottom_color.green, alpha), + alpha_blend_channel(top_color.blue, bottom_color.blue, alpha) + ) + + def parse_single_color(c): if len(c) == 1: c += c diff --git a/kitty/tab_bar.py b/kitty/tab_bar.py index c57183309..29662a873 100644 --- a/kitty/tab_bar.py +++ b/kitty/tab_bar.py @@ -13,20 +13,29 @@ from .fast_data_types import ( from .layout import Rect from .utils import color_as_int from .window import calculate_gl_geometry +from .rgb import alpha_blend -TabBarData = namedtuple('TabBarData', 'title is_active is_last needs_attention') -DrawData = namedtuple('DrawData', 'leading_spaces sep trailing_spaces bell_on_tab bell_fg') +TabBarData = namedtuple('TabBarData', 'title is_active needs_attention') +DrawData = namedtuple('DrawData', 'leading_spaces sep trailing_spaces bell_on_tab bell_fg alpha active_bg inactive_bg default_bg') -def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length): - if draw_data.leading_spaces: - screen.draw(' ' * draw_data.leading_spaces) +def as_rgb(x): + return (x << 8) | 2 + + +def draw_title(draw_data, screen, tab): if tab.needs_attention and draw_data.bell_on_tab: fg = screen.cursor.fg screen.cursor.fg = draw_data.bell_fg screen.draw('🔔 ') screen.cursor.fg = fg screen.draw(tab.title) + + +def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length): + if draw_data.leading_spaces: + screen.draw(' ' * draw_data.leading_spaces) + draw_title(draw_data, screen, tab) if draw_data.trailing_spaces: screen.draw(' ' * draw_data.trailing_spaces) extra = screen.cursor.x - before - max_title_length @@ -40,6 +49,33 @@ def draw_tab_with_separator(draw_data, screen, tab, before, max_title_length): return end +def draw_tab_with_fade(draw_data, screen, tab, before, max_title_length): + tab_bg = draw_data.active_bg if tab.is_active else draw_data.inactive_bg + fade_colors = [as_rgb(color_as_int(alpha_blend(tab_bg, draw_data.default_bg, alpha))) for alpha in draw_data.alpha] + for bg in fade_colors: + screen.cursor.bg = bg + screen.draw(' ') + draw_title(draw_data, screen, tab) + extra = screen.cursor.x - before - max_title_length + if extra > 0: + screen.cursor.x = before + draw_title(draw_data, screen, tab) + extra = screen.cursor.x - before - max_title_length + if extra > 0: + screen.cursor.x -= extra + 1 + screen.draw('…') + for bg in reversed(fade_colors): + if extra >= 0: + break + extra += 1 + screen.cursor.bg = bg + screen.draw(' ') + end = screen.cursor.x + screen.cursor.bg = as_rgb(color_as_int(draw_data.default_bg)) + screen.draw(' ') + return end + + class TabBar: def __init__(self, os_window_id, opts): @@ -70,13 +106,15 @@ class TabBar: self.active_font_style = opts.active_tab_font_style self.inactive_font_style = opts.inactive_tab_font_style - def as_rgb(x): - return (x << 8) | 2 - self.active_bg = as_rgb(color_as_int(opts.active_tab_background)) self.active_fg = as_rgb(color_as_int(opts.active_tab_foreground)) self.bell_fg = as_rgb(0xff0000) - self.draw_data = DrawData(self.leading_spaces, self.sep, self.trailing_spaces, self.opts.bell_on_tab, self.bell_fg) + self.draw_data = DrawData( + self.leading_spaces, self.sep, self.trailing_spaces, self.opts.bell_on_tab, self.bell_fg, + self.opts.tab_fade, self.opts.active_tab_background, self.opts.inactive_tab_background, + self.opts.background + ) + self.draw_func = draw_tab_with_separator if self.opts.tab_bar_style == 'separator' else draw_tab_with_fade def patch_colors(self, spec): if 'active_tab_foreground' in spec: @@ -117,15 +155,16 @@ class TabBar: s.erase_in_line(2, False) max_title_length = (self.screen_geometry.xnum // max(1, len(data))) - 1 cr = [] + last_tab = data[-1] if data else None for t in data: s.cursor.bg = self.active_bg if t.is_active else 0 s.cursor.fg = self.active_fg if t.is_active else 0 s.cursor.bold, s.cursor.italic = self.active_font_style if t.is_active else self.inactive_font_style before = s.cursor.x - end = draw_tab_with_separator(self, s, t, before, max_title_length) + end = self.draw_func(self.draw_data, s, t, before, max_title_length) cr.append((before, end)) - if s.cursor.x > s.columns - max_title_length and not t.is_last: + if s.cursor.x > s.columns - max_title_length and t is not last_tab: s.draw('…') break s.erase_in_line(0, False) # Ensure no long titles bleed after the last tab diff --git a/kitty/tabs.py b/kitty/tabs.py index 865761726..6d7b68ce6 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -473,7 +473,7 @@ class TabManager: # {{{ if w.needs_attention: needs_attention = True break - ans.append(TabBarData(title, t is at, t is self.tabs[-1], needs_attention)) + ans.append(TabBarData(title, t is at, needs_attention)) return ans def activate_tab_at(self, x):