diff --git a/kitty/boss.py b/kitty/boss.py index 59b2f726f..562899ed2 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -220,8 +220,7 @@ class Boss(Thread): def apply_pending_resize(self, w, h): viewport_size.width, viewport_size.height = w, h - for tab in self.tab_manager: - tab.relayout() + self.tab_manager.resize() self.resize_gl_viewport = True self.pending_resize = False glfw_post_empty_event() @@ -367,6 +366,7 @@ class Boss(Thread): tab.render() render_data = {window: window.char_grid.prepare_for_render(self.sprites) for window in tab.visible_windows() if not window.needs_layout} with self.cell_program: + self.tab_manager.render(self.cell_program, self.sprites) for window, rd in render_data.items(): if rd is not None: window.char_grid.render_cells(rd, self.cell_program, self.sprites) diff --git a/kitty/char_grid.py b/kitty/char_grid.py index 661a8f519..31f7581dc 100644 --- a/kitty/char_grid.py +++ b/kitty/char_grid.py @@ -207,6 +207,15 @@ class Selection: # {{{ # }}} +def calculate_gl_geometry(window_geometry): + dx, dy = 2 * cell_size.width / viewport_size.width, 2 * cell_size.height / viewport_size.height + xmargin = window_geometry.left / viewport_size.width + ymargin = window_geometry.top / viewport_size.height + xstart = -1 + 2 * xmargin + ystart = 1 - 2 * ymargin + return ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) + + class CharGrid: url_pat = re.compile('(?:http|https|file|ftp)://\S+', re.IGNORECASE) @@ -237,12 +246,7 @@ class CharGrid: self.sprite_map_type = self.main_sprite_map = self.scroll_sprite_map = self.render_buf = None def update_position(self, window_geometry): - dx, dy = 2 * cell_size.width / viewport_size.width, 2 * cell_size.height / viewport_size.height - xmargin = window_geometry.left / viewport_size.width - ymargin = window_geometry.top / viewport_size.height - xstart = -1 + 2 * xmargin - ystart = 1 - 2 * ymargin - self.screen_geometry = ScreenGeometry(xstart, ystart, window_geometry.xnum, window_geometry.ynum, dx, dy) + self.screen_geometry = calculate_gl_geometry(window_geometry) def resize(self, window_geometry): self.update_position(window_geometry) diff --git a/kitty/tabs.py b/kitty/tabs.py index 620bb9dd6..d34e67cb5 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -3,10 +3,18 @@ # License: GPL v3 Copyright: 2016, Kovid Goyal from collections import deque +from threading import Lock +from ctypes import addressof from .child import Child -from .constants import get_boss, appname, shell_path, cell_size, queue_action -from .fast_data_types import glfw_post_empty_event +from .config import build_ansi_color_table +from .constants import get_boss, appname, shell_path, cell_size, queue_action, viewport_size, WindowGeometry, GLuint +from .fast_data_types import ( + glfw_post_empty_event, Screen, DECAWM, DATA_CELL_SIZE, + ColorProfile, glUniform2ui, glUniform4f, glUniform1i, glUniform2f, + glDrawArraysInstanced, GL_TRIANGLE_FAN +) +from .char_grid import calculate_gl_geometry from .layout import all_layouts from .borders import Borders from .window import Window @@ -14,8 +22,9 @@ from .window import Window class Tab: - def __init__(self, opts, args, session_tab=None): + def __init__(self, opts, args, on_title_change, session_tab=None): self.opts, self.args = opts, args + self.on_title_change = on_title_change self.enabled_layouts = list((session_tab or opts).enabled_layouts) self.borders = Borders(opts) self.windows = deque() @@ -46,6 +55,10 @@ class Tab: def title(self): return getattr(self.active_window, 'title', appname) + def title_changed(self, window): + if window is self.active_window: + self.on_title_change(window.title) + def visible_windows(self): for w in self.windows: if w.is_visible_in_layout: @@ -141,8 +154,28 @@ class TabManager: def __init__(self, opts, args, startup_session): self.opts, self.args = opts, args - self.tabs = [Tab(opts, args, t) for t in startup_session.tabs] + self.tabs = [Tab(opts, args, self.title_changed, t) for t in startup_session.tabs] self.active_tab_idx = startup_session.active_tab_idx + self.tabbar_lock = Lock() + self.tabbar_dirty = True + self.color_profile = ColorProfile() + self.color_profile.update_ansi_color_table(build_ansi_color_table(opts)) + + def resize(self): + for tab in self.tabs: + tab.relayout() + ncells = viewport_size.width // cell_size.width + s = Screen(None, 1, ncells) + s.reset_mode(DECAWM) + self.sprite_map_type = (GLuint * (s.lines * s.columns * DATA_CELL_SIZE)) + with self.tabbar_lock: + self.sprite_map = self.sprite_map_type() + self.tab_bar_screen = s + self.tabbar_dirty = True + margin = (viewport_size.width - ncells * cell_size.width) // 2 + self.screen_geometry = calculate_gl_geometry(WindowGeometry( + margin, viewport_size.height - cell_size.height, viewport_size.width - margin, viewport_size.height, s.columns, s.lines)) + self.screen = s def __iter__(self): return iter(self.tabs) @@ -158,7 +191,46 @@ class TabManager: def tab_bar_height(self): return 0 if len(self.tabs) < 2 else cell_size.height + def title_changed(self, new_title): + with self.tabbar_lock: + self.tabbar_dirty = True + def remove(self, tab): ' Must be called in the GUI thread ' self.tabs.remove(tab) tab.destroy() + + def update_tab_bar_data(self, sprites): + s = self.tab_bar_screen + for t in self.tabs: + title = (t.title or appname) + s.draw(title) + if s.cursor.x > s.columns - 3: + # TODO: Handle trailing wide character + s.cursor.x = s.columns - 4 + s.draw('…') + s.draw(' X┇') + if s.cursor.x > s.columns - 5: + s.draw('…') + break + s.update_cell_data( + sprites.backend, self.color_profile, addressof(self.sprite_map), self.default_fg, self.default_bg, True) + if self.buffer_id is None: + self.buffer_id = sprites.add_sprite_map() + sprites.set_sprite_map(self.buffer_id, self.sprite_map) + + def render(self, cell_program, sprites): + if not hasattr(self, 'screen_geometry') or len(self.tabs) < 2: + return + with self.tabbar_lock: + if self.tabbar_dirty: + self.update_tab_bar_data(sprites) + sprites.bind_sprite_map(self.buffer_id) + ul, sg = cell_program.uniform_location, self.screen_geometry + ul = cell_program.uniform_location + glUniform2ui(ul('dimensions'), sg.xnum, sg.ynum) + glUniform4f(ul('steps'), sg.xstart, sg.ystart, sg.dx, sg.dy) + glUniform1i(ul('sprites'), sprites.sampler_num) + glUniform1i(ul('sprite_map'), sprites.buffer_sampler_num) + glUniform2f(ul('sprite_layout'), *(sprites.layout)) + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, sg.xnum * sg.ynum) diff --git a/kitty/window.py b/kitty/window.py index a88d0f0d1..78dd3adb3 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -115,6 +115,9 @@ class Window: def title_changed(self, new_title): self.title = sanitize_title(new_title or appname) + t = self.tabref() + if t is not None: + t.title_changed(self) glfw_post_empty_event() def icon_changed(self, new_icon):