mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-24 01:08:10 +02:00
Start work on refactoring management of windows
This commit is contained in:
253
kitty/layout/grid.py
Normal file
253
kitty/layout/grid.py
Normal file
@@ -0,0 +1,253 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
from functools import lru_cache
|
||||
from itertools import repeat
|
||||
from typing import Callable, Dict, Generator, List, Optional, Sequence, Tuple
|
||||
|
||||
from kitty.constants import Edges
|
||||
from kitty.typing import WindowType
|
||||
from kitty.window_list import WindowList
|
||||
|
||||
from .base import (
|
||||
Borders, InternalNeighborsMap, Layout, LayoutData, LayoutDimension,
|
||||
ListOfWindows, all_borders, layout_dimension, lgd, no_borders,
|
||||
variable_bias
|
||||
)
|
||||
from .tall import neighbors_for_tall_window
|
||||
|
||||
|
||||
@lru_cache()
|
||||
def calc_grid_size(n: int) -> Tuple[int, int, int, int]:
|
||||
if n <= 5:
|
||||
ncols = 1 if n == 1 else 2
|
||||
else:
|
||||
for ncols in range(3, (n // 2) + 1):
|
||||
if ncols * ncols >= n:
|
||||
break
|
||||
nrows = n // ncols
|
||||
special_rows = n - (nrows * (ncols - 1))
|
||||
special_col = 0 if special_rows < nrows else ncols - 1
|
||||
return ncols, nrows, special_rows, special_col
|
||||
|
||||
|
||||
class Grid(Layout):
|
||||
|
||||
name = 'grid'
|
||||
|
||||
def remove_all_biases(self) -> bool:
|
||||
self.biased_rows: Dict[int, float] = {}
|
||||
self.biased_cols: Dict[int, float] = {}
|
||||
return True
|
||||
|
||||
def column_layout(
|
||||
self,
|
||||
num: int,
|
||||
bias: Optional[Sequence[float]] = None,
|
||||
) -> LayoutDimension:
|
||||
decoration_pairs = tuple(repeat((0, 0), num))
|
||||
return layout_dimension(lgd.central.left, lgd.central.width, lgd.cell_width, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
|
||||
|
||||
def row_layout(
|
||||
self,
|
||||
num: int,
|
||||
bias: Optional[Sequence[float]] = None,
|
||||
) -> LayoutDimension:
|
||||
decoration_pairs = tuple(repeat((0, 0), num))
|
||||
return layout_dimension(lgd.central.top, lgd.central.height, lgd.cell_height, decoration_pairs, bias=bias, left_align=lgd.align_top_left)
|
||||
|
||||
def variable_layout(self, layout_func: Callable[..., LayoutDimension], num_windows: int, biased_map: Dict[int, float]) -> LayoutDimension:
|
||||
return layout_func(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
|
||||
|
||||
def apply_bias(self, idx: int, increment: float, top_level_windows: ListOfWindows, is_horizontal: bool = True) -> bool:
|
||||
b = self.biased_cols if is_horizontal else self.biased_rows
|
||||
num_windows = len(top_level_windows)
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(num_windows)
|
||||
|
||||
def position_for_window_idx(idx: int) -> Tuple[int, int]:
|
||||
row_num = col_num = 0
|
||||
|
||||
def on_col_done(col_windows: List[int]) -> None:
|
||||
nonlocal col_num, row_num
|
||||
row_num = 0
|
||||
col_num += 1
|
||||
|
||||
for window_idx, xl, yl in self.layout_windows(
|
||||
num_windows, nrows, ncols, special_rows, special_col, on_col_done):
|
||||
if idx == window_idx:
|
||||
return row_num, col_num
|
||||
row_num += 1
|
||||
|
||||
row_num, col_num = position_for_window_idx(idx)
|
||||
|
||||
if is_horizontal:
|
||||
b = self.biased_cols
|
||||
if ncols < 2:
|
||||
return False
|
||||
bias_idx = col_num
|
||||
attr = 'biased_cols'
|
||||
|
||||
def layout_func(windows: ListOfWindows, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.column_layout(num_windows, bias=bias)
|
||||
|
||||
else:
|
||||
b = self.biased_rows
|
||||
if max(nrows, special_rows) < 2:
|
||||
return False
|
||||
bias_idx = row_num
|
||||
attr = 'biased_rows'
|
||||
|
||||
def layout_func(windows: ListOfWindows, bias: Optional[Sequence[float]] = None) -> LayoutDimension:
|
||||
return self.row_layout(num_windows, bias=bias)
|
||||
|
||||
before_layout = list(self.variable_layout(layout_func, num_windows, b))
|
||||
candidate = b.copy()
|
||||
before = candidate.get(bias_idx, 0)
|
||||
candidate[bias_idx] = before + increment
|
||||
if before_layout == list(self.variable_layout(layout_func, num_windows, candidate)):
|
||||
return False
|
||||
setattr(self, attr, candidate)
|
||||
return True
|
||||
|
||||
def layout_windows(
|
||||
self,
|
||||
num_windows: int,
|
||||
nrows: int, ncols: int,
|
||||
special_rows: int, special_col: int,
|
||||
on_col_done: Callable[[List[int]], None] = lambda col_windows: None
|
||||
) -> Generator[Tuple[int, LayoutData, LayoutData], None, None]:
|
||||
# Distribute windows top-to-bottom, left-to-right (i.e. in columns)
|
||||
xlayout = self.variable_layout(self.column_layout, ncols, self.biased_cols)
|
||||
yvals_normal = tuple(self.variable_layout(self.row_layout, nrows, self.biased_rows))
|
||||
yvals_special = yvals_normal if special_rows == nrows else tuple(self.variable_layout(self.row_layout, special_rows, self.biased_rows))
|
||||
pos = 0
|
||||
for col in range(ncols):
|
||||
rows = special_rows if col == special_col else nrows
|
||||
yls = yvals_special if col == special_col else yvals_normal
|
||||
xl = next(xlayout)
|
||||
col_windows = []
|
||||
for i, yl in enumerate(yls):
|
||||
window_idx = pos + i
|
||||
yield window_idx, xl, yl
|
||||
col_windows.append(window_idx)
|
||||
pos += rows
|
||||
on_col_done(col_windows)
|
||||
|
||||
def do_layout(self, windows: WindowList, active_window_idx: int) -> None:
|
||||
n = len(windows)
|
||||
if n == 1:
|
||||
self.layout_single_window(windows[0])
|
||||
return
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
|
||||
win_col_map = []
|
||||
|
||||
def on_col_done(col_windows: List[int]) -> None:
|
||||
col_windows_w = [windows[i] for i in col_windows]
|
||||
win_col_map.append(col_windows_w)
|
||||
|
||||
def extents(ld: LayoutData) -> Tuple[int, int]:
|
||||
start = ld.content_pos - ld.space_before
|
||||
size = ld.space_before + ld.space_after + ld.content_size
|
||||
return start, size
|
||||
|
||||
def layout(ld: LayoutData, cell_length: int, before_dec: int, after_dec: int) -> LayoutData:
|
||||
start, size = extents(ld)
|
||||
space_needed_for_decorations = before_dec + after_dec
|
||||
content_size = size - space_needed_for_decorations
|
||||
number_of_cells = content_size // cell_length
|
||||
cell_area = number_of_cells * cell_length
|
||||
extra = content_size - cell_area
|
||||
if extra > 0 and not lgd.align_top_left:
|
||||
before_dec += extra // 2
|
||||
return LayoutData(start + before_dec, number_of_cells, before_dec, size - cell_area - before_dec, cell_area)
|
||||
|
||||
def position_window_in_grid_cell(window_idx: int, xl: LayoutData, yl: LayoutData) -> None:
|
||||
w = windows[window_idx]
|
||||
bw = w.effective_border()
|
||||
edges = Edges(
|
||||
w.effective_margin('left') + w.effective_padding('left') + bw,
|
||||
w.effective_margin('right') + w.effective_padding('right') + bw,
|
||||
w.effective_margin('top') + w.effective_padding('top') + bw,
|
||||
w.effective_margin('bottom') + w.effective_padding('bottom') + bw,
|
||||
)
|
||||
xl = layout(xl, lgd.cell_width, edges.left, edges.right)
|
||||
yl = layout(yl, lgd.cell_height, edges.top, edges.bottom)
|
||||
self.set_window_geometry(w, window_idx, xl, yl)
|
||||
|
||||
for window_idx, xl, yl in self.layout_windows(
|
||||
n, nrows, ncols, special_rows, special_col, on_col_done):
|
||||
position_window_in_grid_cell(window_idx, xl, yl)
|
||||
|
||||
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 needs_borders_map[w.id]:
|
||||
yield all_borders
|
||||
else:
|
||||
yield no_borders
|
||||
|
||||
def window_independent_borders(self, windows: WindowList, active_window: Optional[WindowType] = None) -> Generator[Edges, None, None]:
|
||||
n = len(windows)
|
||||
if not lgd.draw_minimal_borders or n < 2:
|
||||
return
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
row_borders: List[List[Edges]] = [[]]
|
||||
col_borders: List[Edges] = []
|
||||
bw = windows[0].effective_border()
|
||||
|
||||
def on_col_done(col_windows: List[int]) -> None:
|
||||
left = xl.content_pos + xl.content_size + xl.space_after - bw // 2
|
||||
col_borders.append(Edges(left, lgd.central.top, left + bw, lgd.central.bottom))
|
||||
row_borders.append([])
|
||||
|
||||
for window_idx, xl, yl in self.layout_windows(n, nrows, ncols, special_rows, special_col, on_col_done):
|
||||
top = yl.content_pos + yl.content_size + yl.space_after - bw // 2
|
||||
right = xl.content_pos + xl.content_size + xl.space_after
|
||||
row_borders[-1].append(Edges(xl.content_pos - xl.space_before, top, right, top + bw))
|
||||
|
||||
for border in col_borders[:-1]:
|
||||
yield border
|
||||
|
||||
for rows in row_borders:
|
||||
for border in rows[:-1]:
|
||||
yield border
|
||||
|
||||
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap:
|
||||
n = len(windows)
|
||||
if n < 4:
|
||||
return neighbors_for_tall_window(1, window, windows)
|
||||
ncols, nrows, special_rows, special_col = calc_grid_size(n)
|
||||
blank_row: List[Optional[int]] = [None for i in range(ncols)]
|
||||
matrix = tuple(blank_row[:] for j in range(max(nrows, special_rows)))
|
||||
wi = iter(windows)
|
||||
pos_map: Dict[int, Tuple[int, int]] = {}
|
||||
col_counts: List[int] = []
|
||||
for col in range(ncols):
|
||||
rows = special_rows if col == special_col else nrows
|
||||
for row in range(rows):
|
||||
w = next(wi)
|
||||
matrix[row][col] = wid = w.id
|
||||
pos_map[wid] = row, col
|
||||
col_counts.append(rows)
|
||||
row, col = pos_map[window.id]
|
||||
|
||||
def neighbors(row: int, col: int) -> List[int]:
|
||||
try:
|
||||
ans = matrix[row][col]
|
||||
except IndexError:
|
||||
ans = None
|
||||
return [] if ans is None else [ans]
|
||||
|
||||
def side(row: int, col: int, delta: int) -> List[int]:
|
||||
neighbor_col = col + delta
|
||||
if col_counts[neighbor_col] == col_counts[col]:
|
||||
return neighbors(row, neighbor_col)
|
||||
return neighbors(min(row, col_counts[neighbor_col] - 1), neighbor_col)
|
||||
|
||||
return {
|
||||
'top': neighbors(row-1, col) if row else [],
|
||||
'bottom': neighbors(row + 1, col),
|
||||
'left': side(row, col, -1) if col else [],
|
||||
'right': side(row, col, 1) if col < ncols - 1 else [],
|
||||
}
|
||||
Reference in New Issue
Block a user