1#!/usr/local/bin/python3.8 2# vim:fileencoding=utf-8 3# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> 4 5from typing import Any, Dict, Generator, Iterable, List, Tuple 6 7from kitty.borders import BorderColor 8from kitty.types import Edges 9from kitty.typing import WindowType 10from kitty.window_list import WindowGroup, WindowList 11 12from .base import ( 13 BorderLine, Layout, LayoutData, LayoutDimension, NeighborsMap, 14 lgd, variable_bias 15) 16 17 18def borders( 19 data: Iterable[Tuple[WindowGroup, LayoutData, LayoutData]], 20 is_horizontal: bool, 21 all_windows: WindowList, 22 start_offset: int = 1, end_offset: int = 1 23) -> Generator[BorderLine, None, None]: 24 borders: List[BorderLine] = [] 25 active_group = all_windows.active_group 26 needs_borders_map = all_windows.compute_needs_borders_map(lgd.draw_active_borders) 27 try: 28 bw = next(all_windows.iter_all_layoutable_groups()).effective_border() 29 except StopIteration: 30 bw = 0 31 if not bw: 32 return 33 34 for wg, xl, yl in data: 35 if is_horizontal: 36 e1 = Edges( 37 xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, 38 xl.content_pos - xl.space_before + bw, yl.content_pos + yl.content_size + yl.space_after 39 ) 40 e2 = Edges( 41 xl.content_pos + xl.content_size + xl.space_after - bw, yl.content_pos - yl.space_before, 42 xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after 43 ) 44 else: 45 e1 = Edges( 46 xl.content_pos - xl.space_before, yl.content_pos - yl.space_before, 47 xl.content_pos + xl.content_size + xl.space_after, yl.content_pos - yl.space_before + bw 48 ) 49 e2 = Edges( 50 xl.content_pos - xl.space_before, yl.content_pos + yl.content_size + yl.space_after - bw, 51 xl.content_pos + xl.content_size + xl.space_after, yl.content_pos + yl.content_size + yl.space_after 52 ) 53 color = BorderColor.inactive 54 if needs_borders_map.get(wg.id): 55 color = BorderColor.active if wg is active_group else BorderColor.bell 56 borders.append(BorderLine(e1, color)) 57 borders.append(BorderLine(e2, color)) 58 59 last_idx = len(borders) - 1 - end_offset 60 for i, x in enumerate(borders): 61 if start_offset <= i <= last_idx: 62 yield x 63 64 65class Vertical(Layout): 66 67 name = 'vertical' 68 main_is_horizontal = False 69 no_minimal_window_borders = True 70 main_axis_layout = Layout.ylayout 71 perp_axis_layout = Layout.xlayout 72 73 def variable_layout(self, all_windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension: 74 num_windows = all_windows.num_groups 75 bias = variable_bias(num_windows, biased_map) if num_windows else None 76 return self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=bias) 77 78 def fixed_layout(self, wg: WindowGroup) -> LayoutDimension: 79 return self.perp_axis_layout(iter((wg,)), border_mult=0 if lgd.draw_minimal_borders else 1) 80 81 def remove_all_biases(self) -> bool: 82 self.biased_map: Dict[int, float] = {} 83 return True 84 85 def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool: 86 if self.main_is_horizontal != is_horizontal: 87 return False 88 num_windows = all_windows.num_groups 89 if num_windows < 2: 90 return False 91 before_layout = list(self.variable_layout(all_windows, self.biased_map)) 92 candidate = self.biased_map.copy() 93 before = candidate.get(idx, 0) 94 candidate[idx] = before + increment 95 if before_layout == list(self.variable_layout(all_windows, candidate)): 96 return False 97 self.biased_map = candidate 98 return True 99 100 def generate_layout_data(self, all_windows: WindowList) -> Generator[Tuple[WindowGroup, LayoutData, LayoutData], None, None]: 101 ylayout = self.variable_layout(all_windows, self.biased_map) 102 for wg, yl in zip(all_windows.iter_all_layoutable_groups(), ylayout): 103 xl = next(self.fixed_layout(wg)) 104 if self.main_is_horizontal: 105 xl, yl = yl, xl 106 yield wg, xl, yl 107 108 def do_layout(self, all_windows: WindowList) -> None: 109 window_count = all_windows.num_groups 110 if window_count == 1: 111 self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) 112 return 113 for wg, xl, yl in self.generate_layout_data(all_windows): 114 self.set_window_group_geometry(wg, xl, yl) 115 116 def minimal_borders(self, all_windows: WindowList) -> Generator[BorderLine, None, None]: 117 window_count = all_windows.num_groups 118 if window_count < 2 or not lgd.draw_minimal_borders: 119 return 120 yield from borders(self.generate_layout_data(all_windows), self.main_is_horizontal, all_windows) 121 122 def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap: 123 wg = all_windows.group_for_window(window) 124 assert wg is not None 125 groups = tuple(all_windows.iter_all_layoutable_groups()) 126 idx = groups.index(wg) 127 before = [] if wg is groups[0] else [groups[idx-1].id] 128 after = [] if wg is groups[-1] else [groups[idx+1].id] 129 if self.main_is_horizontal: 130 return {'left': before, 'right': after, 'top': [], 'bottom': []} 131 return {'top': before, 'bottom': after, 'left': [], 'right': []} 132 133 def layout_state(self) -> Dict[str, Any]: 134 return {'biased_map': self.biased_map} 135 136 137class Horizontal(Vertical): 138 139 name = 'horizontal' 140 main_is_horizontal = True 141 main_axis_layout = Layout.xlayout 142 perp_axis_layout = Layout.ylayout 143