diff --git a/kitty/borders.py b/kitty/borders.py index cb5f41675..a07c61322 100644 --- a/kitty/borders.py +++ b/kitty/borders.py @@ -4,15 +4,16 @@ from enum import IntFlag from itertools import chain -from typing import List, Optional, Sequence, Tuple +from typing import Sequence, Tuple from .fast_data_types import ( BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, os_window_has_background_image ) from .options_stub import Options +from .typing import LayoutType from .utils import load_shaders -from .typing import WindowType, LayoutType +from .window_list import WindowGroup, WindowList class BorderColor(IntFlag): @@ -30,10 +31,12 @@ def horizontal_edge(os_window_id: int, tab_id: int, color: int, height: int, lef add_borders_rect(os_window_id, tab_id, left, top, right, top + height, color) -def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: WindowType, borders: bool = False) -> None: - geometry = window.geometry - pl, pt = window.effective_padding('left'), window.effective_padding('top') - pr, pb = window.effective_padding('right'), window.effective_padding('bottom') +def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: WindowGroup, borders: bool = False) -> None: + geometry = wg.geometry + if geometry is None: + return + pl, pt = wg.effective_padding('left'), wg.effective_padding('top') + pr, pb = wg.effective_padding('right'), wg.effective_padding('bottom') left = geometry.left - pl top = geometry.top - pt lr = geometry.right @@ -41,7 +44,7 @@ def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], window: Wi bt = geometry.bottom bottom = bt + pb if borders: - width = window.effective_border() + width = wg.effective_border() bt = bottom lr = right left -= width @@ -69,8 +72,7 @@ class Borders: def __call__( self, - windows: List[WindowType], - active_window: Optional[WindowType], + all_windows: WindowList, current_layout: LayoutType, extra_blank_rects: Sequence[Tuple[int, int, int, int]], draw_window_borders: bool = True, @@ -82,31 +84,33 @@ class Borders: left, top, right, bottom = br add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg) bw = 0 - if windows: - bw = windows[0].effective_border() + groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True)) + if groups: + bw = groups[0].effective_border() draw_borders = bw > 0 and draw_window_borders if draw_borders: - border_data = current_layout.resolve_borders(windows, active_window) + border_data = current_layout.resolve_borders(all_windows) + active_group = all_windows.active_group - for i, w in enumerate(windows): - window_bg = w.screen.color_profile.default_bg + for i, wg in enumerate(groups): + window_bg = wg.default_bg window_bg = (window_bg << 8) | BorderColor.window_bg if draw_borders: # Draw the border rectangles - if w is active_window and self.draw_active_borders: + if wg is active_group and self.draw_active_borders: color = BorderColor.active else: - color = BorderColor.bell if w.needs_attention else BorderColor.inactive + color = BorderColor.bell if wg.needs_attention else BorderColor.inactive try: colors = tuple(color if needed else window_bg for needed in next(border_data)) - draw_edges(self.os_window_id, self.tab_id, colors, w, borders=True) + draw_edges(self.os_window_id, self.tab_id, colors, wg, borders=True) except StopIteration: pass if not has_background_image: # Draw the background rectangles over the padding region colors = window_bg, window_bg, window_bg, window_bg - draw_edges(self.os_window_id, self.tab_id, colors, w) + draw_edges(self.os_window_id, self.tab_id, colors, wg) color = BorderColor.inactive - for (left, top, right, bottom) in current_layout.window_independent_borders(windows, active_window): + for (left, top, right, bottom) in current_layout.window_independent_borders(all_windows): add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, color) diff --git a/kitty/layout/base.py b/kitty/layout/base.py index b9bd0c84b..1cb3da316 100644 --- a/kitty/layout/base.py +++ b/kitty/layout/base.py @@ -4,10 +4,9 @@ from functools import partial from itertools import repeat -from operator import attrgetter from typing import ( - Dict, Generator, Iterable, List, NamedTuple, Optional, Sequence, Tuple, - Union, cast + Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, Sequence, + Tuple, Union, cast ) from kitty.constants import Edges, WindowGeometry @@ -47,20 +46,13 @@ LayoutDimension = Generator[LayoutData, None, None] ListOfWindows = List[WindowType] -class InternalNeighborsMap(TypedDict): +class NeighborsMap(TypedDict): left: List[int] top: List[int] right: List[int] bottom: List[int] -class NeighborsMap(TypedDict): - left: Tuple[int, ...] - top: Tuple[int, ...] - right: Tuple[int, ...] - bottom: Tuple[int, ...] - - class LayoutGlobalData: draw_minimal_borders: bool = True draw_active_borders: bool = True @@ -241,7 +233,10 @@ class Layout: return False def modify_size_of_window(self, all_windows: WindowList, window_id: int, increment: float, is_horizontal: bool = True) -> bool: - return self.apply_bias(window_id, increment, all_windows, is_horizontal) + idx = all_windows.group_idx_for_window(window_id) + if idx is None: + return False + return self.apply_bias(idx, increment, all_windows, is_horizontal) def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts: data: Dict[str, str] = {} @@ -264,20 +259,12 @@ class Layout: def neighbors(self, all_windows: WindowList) -> NeighborsMap: w = all_windows.active_window assert w is not None - n = self.neighbors_for_window(w, all_windows) - id_getter = attrgetter('id') - ans: NeighborsMap = { - 'left': tuple(map(id_getter, n['left'])), - 'top': tuple(map(id_getter, n['top'])), - 'right': tuple(map(id_getter, n['right'])), - 'bottom': tuple(map(id_getter, n['bottom'])) - } - return ans + return self.neighbors_for_window(w, all_windows) def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool: # delta can be either a number or a string such as 'left', 'top', etc # for neighborhood moves - if len(all_windows) < 2 or not delta: + if all_windows.num_groups < 2 or not delta: return False if isinstance(delta, int): @@ -291,7 +278,7 @@ class Layout: q: List[int] = cast(List[int], neighbors.get(which, [])) if not q: return False - return all_windows.move_window_group(to_group_with_window_id=q[0]) + return all_windows.move_window_group(to_group=q[0]) def add_window(self, all_windows: WindowList, window: WindowType, location: Optional[str] = None, overlay_for: Optional[int] = None) -> None: if overlay_for is not None and overlay_for in all_windows: @@ -349,7 +336,7 @@ class Layout: def xlayout( self, - all_windows: WindowList, + groups: Iterator[WindowGroup], bias: Optional[Sequence[float]] = None, start: Optional[int] = None, size: Optional[int] = None, @@ -357,7 +344,7 @@ class Layout: ) -> LayoutDimension: decoration_pairs = tuple( (g.decoration('left'), g.decoration('right')) for i, g in - enumerate(all_windows.iter_all_layoutable_groups()) if i >= offset + enumerate(groups) if i >= offset ) if start is None: start = lgd.central.left @@ -367,7 +354,7 @@ class Layout: def ylayout( self, - all_windows: WindowList, + groups: Iterator[WindowGroup], bias: Optional[Sequence[float]] = None, start: Optional[int] = None, size: Optional[int] = None, @@ -375,7 +362,7 @@ class Layout: ) -> LayoutDimension: decoration_pairs = tuple( (g.decoration('top'), g.decoration('bottom')) for i, g in - enumerate(all_windows.iter_all_layoutable_groups()) if i >= offset + enumerate(groups) if i >= offset ) if start is None: start = lgd.central.top @@ -383,37 +370,35 @@ class Layout: size = lgd.central.height return layout_dimension(start, size, lgd.cell_height, decoration_pairs, bias=bias, left_align=lgd.align_top_left) - def set_window_geometry(self, w: WindowType, idx: int, xl: LayoutData, yl: LayoutData) -> None: - wg = window_geometry_from_layouts(xl, yl) - w.set_geometry(wg) - self.blank_rects.extend(blank_rects_for_window(wg)) + def set_window_group_geometry(self, wg: WindowGroup, xl: LayoutData, yl: LayoutData) -> WindowGeometry: + geom = window_geometry_from_layouts(xl, yl) + wg.set_geometry(geom) + self.blank_rects.extend(blank_rects_for_window(geom)) + return geom def do_layout(self, windows: WindowList) -> None: raise NotImplementedError() - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: return {'left': [], 'right': [], 'top': [], 'bottom': []} - def compute_needs_borders_map(self, windows: WindowList, active_window: Optional[WindowType]) -> Dict[int, bool]: - return {w.id: ((w is active_window and lgd.draw_active_borders) or w.needs_attention) for w in windows} + def compute_needs_borders_map(self, all_windows: WindowList) -> Dict[int, bool]: + return all_windows.compute_needs_borders_map(lgd.draw_active_borders) - def resolve_borders(self, windows: WindowList, active_window: Optional[WindowType]) -> Generator[Borders, None, None]: + def resolve_borders(self, windows: WindowList) -> Generator[Borders, None, None]: if lgd.draw_minimal_borders: - needs_borders_map = self.compute_needs_borders_map(windows, active_window) - yield from self.minimal_borders(windows, active_window, needs_borders_map) + needs_borders_map = self.compute_needs_borders_map(windows) + yield from self.minimal_borders(windows, needs_borders_map) else: - yield from Layout.minimal_borders(self, windows, active_window, {}) + yield from Layout.minimal_borders(self, windows, {}) def window_independent_borders(self, windows: WindowList, active_window: Optional[WindowType] = None) -> Generator[Edges, None, None]: return yield Edges() # type: ignore - def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: - for w in windows: - if w is not active_window or lgd.draw_active_borders or w.needs_attention: - yield all_borders - else: - yield no_borders + def minimal_borders(self, windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: + for needs_border in needs_borders_map.values(): + yield all_borders if needs_border else no_borders def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]: pass diff --git a/kitty/layout/grid.py b/kitty/layout/grid.py index 71923b754..89577bcd2 100644 --- a/kitty/layout/grid.py +++ b/kitty/layout/grid.py @@ -11,7 +11,7 @@ from kitty.typing import WindowType from kitty.window_list import WindowList from .base import ( - Borders, InternalNeighborsMap, Layout, LayoutData, LayoutDimension, + Borders, NeighborsMap, Layout, LayoutData, LayoutDimension, ListOfWindows, all_borders, layout_dimension, lgd, no_borders, variable_bias ) @@ -213,7 +213,7 @@ class Grid(Layout): for border in rows[:-1]: yield border - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: n = len(windows) if n < 4: return neighbors_for_tall_window(1, window, windows) diff --git a/kitty/layout/splits.py b/kitty/layout/splits.py index 8d8b32cba..df6b857ef 100644 --- a/kitty/layout/splits.py +++ b/kitty/layout/splits.py @@ -11,7 +11,7 @@ from kitty.typing import EdgeLiteral, WindowType from kitty.window_list import WindowList from .base import ( - Borders, InternalNeighborsMap, Layout, LayoutOpts, all_borders, + Borders, NeighborsMap, Layout, LayoutOpts, all_borders, blank_rects_for_window, lgd, no_borders, window_geometry_from_layouts ) @@ -234,7 +234,7 @@ class Pair: return parent.modify_size_of_child(which, increment, is_horizontal, layout_object) return False - def neighbors_for_window(self, window_id: int, ans: InternalNeighborsMap, layout_object: 'Splits') -> None: + def neighbors_for_window(self, window_id: int, ans: NeighborsMap, layout_object: 'Splits') -> None: def quadrant(is_horizontal: bool, is_first: bool) -> Tuple[EdgeLiteral, EdgeLiteral]: if is_horizontal: @@ -407,10 +407,10 @@ class Splits(Layout): if pair.between_border is not None: yield pair.between_border - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: window_id = window.overlay_for or window.id pair = self.pairs_root.pair_for_window(window_id) - ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} + ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []} if pair is not None: pair.neighbors_for_window(window_id, ans, self) return ans diff --git a/kitty/layout/tall.py b/kitty/layout/tall.py index 067fda8f7..6dc3914b9 100644 --- a/kitty/layout/tall.py +++ b/kitty/layout/tall.py @@ -3,30 +3,32 @@ # License: GPLv3 Copyright: 2020, Kovid Goyal from itertools import repeat -from typing import Dict, Generator, List, Optional, Tuple +from typing import Dict, Generator, List, Tuple from kitty.typing import EdgeLiteral, WindowType from kitty.window_list import WindowList from .base import ( - Borders, InternalNeighborsMap, Layout, LayoutDimension, LayoutOpts, - ListOfWindows, all_borders, lgd, no_borders, normalize_biases, - safe_increment_bias, variable_bias + Borders, Layout, LayoutDimension, LayoutOpts, NeighborsMap, all_borders, + lgd, no_borders, normalize_biases, safe_increment_bias, variable_bias ) -def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, windows: WindowList) -> InternalNeighborsMap: - idx = windows.index(window) - prev = None if idx == 0 else windows[idx-1] - nxt = None if idx == len(windows) - 1 else windows[idx+1] - ans: InternalNeighborsMap = {'left': [prev.id] if prev is not None else [], 'right': [], 'top': [], 'bottom': []} +def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, all_windows: WindowList) -> NeighborsMap: + wg = all_windows.group_for_window(window) + assert wg is not None + groups = tuple(all_windows.iter_all_layoutable_groups()) + idx = groups.index(wg) + prev = None if idx == 0 else groups[idx-1] + nxt = None if idx == len(groups) - 1 else groups[idx+1] + ans: NeighborsMap = {'left': [prev.id] if prev is not None else [], 'right': [], 'top': [], 'bottom': []} if idx < num_full_size_windows - 1: if nxt is not None: ans['right'] = [nxt.id] elif idx == num_full_size_windows - 1: - ans['right'] = [w.id for w in windows[idx+1:]] + ans['right'] = [w.id for w in groups[idx+1:]] else: - ans['left'] = [windows[num_full_size_windows - 1].id] + ans['left'] = [groups[num_full_size_windows - 1].id] if idx > num_full_size_windows and prev is not None: ans['top'] = [prev.id] if nxt is not None: @@ -72,13 +74,13 @@ class Tall(Layout): self.biased_map: Dict[int, float] = {} return True - def variable_layout(self, windows: ListOfWindows, biased_map: Dict[int, float]) -> LayoutDimension: - windows = windows[self.num_full_size_windows:] - bias = variable_bias(len(windows), biased_map) if len(windows) > 1 else None - return self.perp_axis_layout(windows, bias=bias) + def variable_layout(self, all_windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension: + num = all_windows.num_groups - self.num_full_size_windows + bias = variable_bias(num, biased_map) if num > 1 else None + return self.perp_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias, offset=self.num_full_size_windows) - def apply_bias(self, idx: int, increment: float, top_level_windows: ListOfWindows, is_horizontal: bool = True) -> bool: - num_windows = len(top_level_windows) + def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: + num_windows = all_windows.num_groups if self.main_is_horizontal == is_horizontal: before_main_bias = self.main_bias ncols = self.num_full_size_windows + 1 @@ -93,63 +95,67 @@ class Tall(Layout): if idx < self.num_full_size_windows or num_of_short_windows < 2: return False idx -= self.num_full_size_windows - before_layout = list(self.variable_layout(top_level_windows, self.biased_map)) + before_layout = list(self.variable_layout(all_windows, self.biased_map)) before = self.biased_map.get(idx, 0.) candidate = self.biased_map.copy() candidate[idx] = after = before + increment - if before_layout == list(self.variable_layout(top_level_windows, candidate)): + if before_layout == list(self.variable_layout(all_windows, candidate)): return False self.biased_map = candidate return before != after - def do_layout(self, windows: WindowList, active_window_idx: int) -> None: - if len(windows) == 1: - self.layout_single_window(windows[0]) + def do_layout(self, all_windows: WindowList) -> None: + num = all_windows.num_groups + if num == 1: + self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) return is_fat = not self.main_is_horizontal - if len(windows) <= self.num_full_size_windows + 1: - xlayout = self.main_axis_layout(windows, bias=self.main_bias) - for i, (w, xl) in enumerate(zip(windows, xlayout)): - yl = next(self.perp_axis_layout([w])) + if num <= self.num_full_size_windows + 1: + xlayout = self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=self.main_bias) + for i, (wg, xl) in enumerate(zip(all_windows.iter_all_layoutable_groups(), xlayout)): + yl = next(self.perp_axis_layout(iter((wg,)))) if is_fat: xl, yl = yl, xl - self.set_window_geometry(w, i, xl, yl) + self.set_window_group_geometry(wg, xl, yl) return - xlayout = self.main_axis_layout(windows[:self.num_full_size_windows + 1], bias=self.main_bias) + main_axis_groups = (gr for i, gr in enumerate(all_windows.iter_all_layoutable_groups()) if i <= self.num_full_size_windows) + xlayout = self.main_axis_layout(main_axis_groups, bias=self.main_bias) attr: EdgeLiteral = 'bottom' if is_fat else 'right' start = lgd.central.top if is_fat else lgd.central.left - for i, w in enumerate(windows): + for i, wg in enumerate(all_windows.iter_all_layoutable_groups()): if i >= self.num_full_size_windows: break xl = next(xlayout) - yl = next(self.perp_axis_layout([w])) + yl = next(self.perp_axis_layout(iter((wg,)))) if is_fat: xl, yl = yl, xl - self.set_window_geometry(w, i, xl, yl) - start = getattr(w.geometry, attr) + w.effective_border() + w.effective_margin(attr) + w.effective_padding(attr) - ylayout = self.variable_layout(windows, self.biased_map) + geom = self.set_window_group_geometry(wg, xl, yl) + start = getattr(geom, attr) + wg.decoration(attr) + ylayout = self.variable_layout(all_windows, self.biased_map) size = (lgd.central.height if is_fat else lgd.central.width) - start - for i, w in enumerate(windows): + for i, wg in enumerate(all_windows.iter_all_layoutable_groups()): if i < self.num_full_size_windows: continue yl = next(ylayout) - xl = next(self.main_axis_layout([w], start=start, size=size)) + xl = next(self.main_axis_layout(iter((wg,)), start=start, size=size)) if is_fat: xl, yl = yl, xl - self.set_window_geometry(w, i, xl, yl) + self.set_window_group_geometry(wg, xl, yl) - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: return neighbors_for_tall_window(self.num_full_size_windows, window, windows) - def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: - last_i = len(windows) - 1 - for i, w in enumerate(windows): - if needs_borders_map[w.id]: + def minimal_borders(self, all_windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: + num = all_windows.num_groups + last_i = num - 1 + groups = tuple(all_windows.iter_all_layoutable_groups()) + for i, wg in enumerate(groups): + if needs_borders_map[wg.id]: yield all_borders continue if i < self.num_full_size_windows: - if (last_i == i+1 or i+1 < self.num_full_size_windows) and needs_borders_map[windows[i+1].id]: + if (last_i == i+1 or i+1 < self.num_full_size_windows) and needs_borders_map[groups[i+1].id]: yield no_borders else: yield no_borders if i == last_i else self.only_main_border @@ -157,7 +163,7 @@ class Tall(Layout): if i == last_i: yield no_borders break - if needs_borders_map[windows[i+1].id]: + if needs_borders_map[groups[i+1].id]: yield no_borders else: yield self.only_between_border @@ -172,18 +178,21 @@ class Fat(Tall): main_axis_layout = Layout.ylayout perp_axis_layout = Layout.xlayout - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: - idx = windows.index(window) - prev = None if idx == 0 else windows[idx-1] - nxt = None if idx == len(windows) - 1 else windows[idx+1] - ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [] if prev is None else [prev.id], 'bottom': []} + def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: + wg = all_windows.group_for_window(window) + assert wg is not None + groups = tuple(all_windows.iter_all_layoutable_groups()) + idx = groups.index(wg) + prev = None if idx == 0 else groups[idx-1] + nxt = None if idx == len(groups) - 1 else groups[idx+1] + ans: NeighborsMap = {'left': [], 'right': [], 'top': [] if prev is None else [prev.id], 'bottom': []} if idx < self.num_full_size_windows - 1: if nxt is not None: ans['bottom'] = [nxt.id] elif idx == self.num_full_size_windows - 1: - ans['bottom'] = [w.id for w in windows[idx+1:]] + ans['bottom'] = [w.id for w in groups[idx+1:]] else: - ans['top'] = [windows[self.num_full_size_windows - 1].id] + ans['top'] = [groups[self.num_full_size_windows - 1].id] if idx > self.num_full_size_windows and prev is not None: ans['left'] = [prev.id] if nxt is not None: diff --git a/kitty/layout/vertical.py b/kitty/layout/vertical.py index 535cfcea7..a4d6d3cba 100644 --- a/kitty/layout/vertical.py +++ b/kitty/layout/vertical.py @@ -8,7 +8,7 @@ from kitty.typing import WindowType from kitty.window_list import WindowList from .base import ( - Borders, InternalNeighborsMap, Layout, LayoutDimension, ListOfWindows, + Borders, NeighborsMap, Layout, LayoutDimension, ListOfWindows, all_borders, no_borders, variable_bias ) @@ -72,7 +72,7 @@ class Vertical(Layout): else: yield self.only_between_border - def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: + def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: idx = windows.index(window) before = [] if window is windows[0] else [windows[idx-1].id] after = [] if window is windows[-1] else [windows[idx+1].id] diff --git a/kitty/tabs.py b/kitty/tabs.py index 3bd4d34ae..32c6ac207 100644 --- a/kitty/tabs.py +++ b/kitty/tabs.py @@ -188,11 +188,6 @@ class Tab: # {{{ def on_bell(self, window: Window) -> None: self.mark_tab_bar_dirty() - def visible_windows(self) -> Generator[Window, None, None]: - for w in self.windows: - if w.is_visible_in_layout: - yield w - def relayout(self) -> None: if self.windows: self.current_layout(self.windows) @@ -201,13 +196,12 @@ class Tab: # {{{ def relayout_borders(self) -> None: tm = self.tab_manager_ref() if tm is not None: - visible_windows = list(self.visible_windows()) w = self.active_window ly = self.current_layout self.borders( - windows=visible_windows, active_window=w, + all_windows=self.windows, current_layout=ly, extra_blank_rects=tm.blank_rects, - draw_window_borders=(ly.needs_window_borders and len(visible_windows) > 1) or ly.must_draw_borders + draw_window_borders=(ly.needs_window_borders and self.windows.num_visble_groups > 1) or ly.must_draw_borders ) if w is not None: w.change_titlebar_color() @@ -420,9 +414,9 @@ class Tab: # {{{ neighbors = self.current_layout.neighbors(self.windows) candidates = cast(Optional[Tuple[int, ...]], neighbors.get(which)) if candidates: - self.windows.set_active_window_group_for(candidates[0]) + self.windows.set_active_group(candidates[0]) - def move_window(self, delta: int = 1) -> None: + def move_window(self, delta: Union[str, int] = 1) -> None: if self.current_layout.move_window(self.windows, delta): self.relayout() diff --git a/kitty/window_list.py b/kitty/window_list.py index ad4aef63c..76638b609 100644 --- a/kitty/window_list.py +++ b/kitty/window_list.py @@ -44,6 +44,13 @@ class WindowGroup: return True return False + @property + def needs_attention(self) -> bool: + for w in self.windows: + if w.needs_attention: + return True + return False + @property def base_window_id(self) -> int: return self.windows[0].id if self.windows else 0 @@ -71,10 +78,39 @@ class WindowGroup: w = self.windows[0] return w.effective_margin(which, is_single_window=is_single_window) + w.effective_border() * border_mult + w.effective_padding(which) + def effective_padding(self, which: EdgeLiteral) -> int: + if not self.windows: + return 0 + w = self.windows[0] + return w.effective_padding(which) + + def effective_border(self) -> int: + if not self.windows: + return 0 + w = self.windows[0] + return w.effective_border() + def set_geometry(self, geom: WindowGeometry) -> None: for w in self.windows: w.set_geometry(geom) + @property + def default_bg(self) -> int: + if self.windows: + return self.windows[-1].screen.color_profile.default_bg + return 0 + + @property + def geometry(self) -> Optional[WindowGeometry]: + if self.windows: + return self.windows[-1].geometry + + @property + def is_visible_in_layout(self) -> bool: + if not self.windows: + return False + return self.windows[-1].is_visible_in_layout + class WindowList: @@ -149,6 +185,11 @@ class WindowList: changed = True return changed + def set_active_group(self, group_id: int) -> bool: + for i, gr in enumerate(self.groups): + if gr.id == group_id: + return self.set_active_group_idx(i) + def change_tab(self, tab: TabType) -> None: self.tabref = weakref.ref(tab) @@ -158,8 +199,8 @@ class WindowList: for window in g: yield window, window.id == aw - def iter_all_layoutable_groups(self) -> Iterator[WindowGroup]: - return iter(self.groups) + def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]: + return iter(g for g in self.groups if g.is_visible_in_layout) if only_visible else iter(self.groups) def make_previous_group_active(self, which: int = 1, notify: bool = False) -> None: which = max(1, which) @@ -186,6 +227,12 @@ class WindowList: if q in g: return g + def group_idx_for_window(self, x: WindowOrId) -> Optional[int]: + q = self.id_map[x] if isinstance(x, int) else x + for i, g in enumerate(self.groups): + if q in g: + return i + def windows_in_group_of(self, x: WindowOrId) -> Iterator[WindowType]: g = self.group_for_window(x) if g is not None: @@ -289,16 +336,15 @@ class WindowList: def activate_next_window_group(self, delta: int) -> None: self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_groups, delta)) - def move_window_group(self, by: Optional[int] = None, to_group_with_window_id: Optional[int] = None) -> bool: + def move_window_group(self, by: Optional[int] = None, to_group: Optional[int] = None) -> bool: if self.active_group_idx < 0 or not self.groups: return False target = -1 if by is not None: target = wrap_increment(self.active_group_idx, self.num_groups, by) - if to_group_with_window_id is not None: - q = self.id_map[to_group_with_window_id] + if to_group is not None: for i, group in enumerate(self.groups): - if q in group: + if group.id == to_group: target = i break if target > -1: @@ -308,3 +354,15 @@ class WindowList: self.set_active_group_idx(target) return True return False + + def compute_needs_borders_map(self, draw_active_borders: bool) -> Dict[int, bool]: + ag = self.active_group + return {gr.id: ((gr is ag and draw_active_borders) or gr.needs_attention) for gr in self.groups} + + @property + def num_visble_groups(self) -> int: + ans = 0 + for gr in self.groups: + if gr.is_visible_in_layout: + ans += 1 + return ans