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