Tall and Fat layouts ported to new groups API

This commit is contained in:
Kovid Goyal 2020-05-07 09:54:26 +05:30
parent e844ad6db3
commit 01c0e8da93
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
8 changed files with 187 additions and 137 deletions

View File

@ -4,15 +4,16 @@
from enum import IntFlag from enum import IntFlag
from itertools import chain from itertools import chain
from typing import List, Optional, Sequence, Tuple from typing import Sequence, Tuple
from .fast_data_types import ( from .fast_data_types import (
BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program, BORDERS_PROGRAM, add_borders_rect, compile_program, init_borders_program,
os_window_has_background_image os_window_has_background_image
) )
from .options_stub import Options from .options_stub import Options
from .typing import LayoutType
from .utils import load_shaders from .utils import load_shaders
from .typing import WindowType, LayoutType from .window_list import WindowGroup, WindowList
class BorderColor(IntFlag): 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) 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: def draw_edges(os_window_id: int, tab_id: int, colors: Sequence[int], wg: WindowGroup, borders: bool = False) -> None:
geometry = window.geometry geometry = wg.geometry
pl, pt = window.effective_padding('left'), window.effective_padding('top') if geometry is None:
pr, pb = window.effective_padding('right'), window.effective_padding('bottom') 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 left = geometry.left - pl
top = geometry.top - pt top = geometry.top - pt
lr = geometry.right 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 bt = geometry.bottom
bottom = bt + pb bottom = bt + pb
if borders: if borders:
width = window.effective_border() width = wg.effective_border()
bt = bottom bt = bottom
lr = right lr = right
left -= width left -= width
@ -69,8 +72,7 @@ class Borders:
def __call__( def __call__(
self, self,
windows: List[WindowType], all_windows: WindowList,
active_window: Optional[WindowType],
current_layout: LayoutType, current_layout: LayoutType,
extra_blank_rects: Sequence[Tuple[int, int, int, int]], extra_blank_rects: Sequence[Tuple[int, int, int, int]],
draw_window_borders: bool = True, draw_window_borders: bool = True,
@ -82,31 +84,33 @@ class Borders:
left, top, right, bottom = br left, top, right, bottom = br
add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg) add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, BorderColor.default_bg)
bw = 0 bw = 0
if windows: groups = tuple(all_windows.iter_all_layoutable_groups(only_visible=True))
bw = windows[0].effective_border() if groups:
bw = groups[0].effective_border()
draw_borders = bw > 0 and draw_window_borders draw_borders = bw > 0 and draw_window_borders
if draw_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): for i, wg in enumerate(groups):
window_bg = w.screen.color_profile.default_bg window_bg = wg.default_bg
window_bg = (window_bg << 8) | BorderColor.window_bg window_bg = (window_bg << 8) | BorderColor.window_bg
if draw_borders: if draw_borders:
# Draw the border rectangles # 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 color = BorderColor.active
else: else:
color = BorderColor.bell if w.needs_attention else BorderColor.inactive color = BorderColor.bell if wg.needs_attention else BorderColor.inactive
try: try:
colors = tuple(color if needed else window_bg for needed in next(border_data)) 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: except StopIteration:
pass pass
if not has_background_image: if not has_background_image:
# Draw the background rectangles over the padding region # Draw the background rectangles over the padding region
colors = window_bg, window_bg, window_bg, window_bg 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 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) add_borders_rect(self.os_window_id, self.tab_id, left, top, right, bottom, color)

View File

@ -4,10 +4,9 @@
from functools import partial from functools import partial
from itertools import repeat from itertools import repeat
from operator import attrgetter
from typing import ( from typing import (
Dict, Generator, Iterable, List, NamedTuple, Optional, Sequence, Tuple, Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, Sequence,
Union, cast Tuple, Union, cast
) )
from kitty.constants import Edges, WindowGeometry from kitty.constants import Edges, WindowGeometry
@ -47,20 +46,13 @@ LayoutDimension = Generator[LayoutData, None, None]
ListOfWindows = List[WindowType] ListOfWindows = List[WindowType]
class InternalNeighborsMap(TypedDict): class NeighborsMap(TypedDict):
left: List[int] left: List[int]
top: List[int] top: List[int]
right: List[int] right: List[int]
bottom: List[int] bottom: List[int]
class NeighborsMap(TypedDict):
left: Tuple[int, ...]
top: Tuple[int, ...]
right: Tuple[int, ...]
bottom: Tuple[int, ...]
class LayoutGlobalData: class LayoutGlobalData:
draw_minimal_borders: bool = True draw_minimal_borders: bool = True
draw_active_borders: bool = True draw_active_borders: bool = True
@ -241,7 +233,10 @@ class Layout:
return False return False
def modify_size_of_window(self, all_windows: WindowList, window_id: int, increment: float, is_horizontal: bool = True) -> bool: 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: def parse_layout_opts(self, layout_opts: Optional[str] = None) -> LayoutOpts:
data: Dict[str, str] = {} data: Dict[str, str] = {}
@ -264,20 +259,12 @@ class Layout:
def neighbors(self, all_windows: WindowList) -> NeighborsMap: def neighbors(self, all_windows: WindowList) -> NeighborsMap:
w = all_windows.active_window w = all_windows.active_window
assert w is not None assert w is not None
n = self.neighbors_for_window(w, all_windows) return 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
def move_window(self, all_windows: WindowList, delta: Union[str, int] = 1) -> bool: 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 # delta can be either a number or a string such as 'left', 'top', etc
# for neighborhood moves # for neighborhood moves
if len(all_windows) < 2 or not delta: if all_windows.num_groups < 2 or not delta:
return False return False
if isinstance(delta, int): if isinstance(delta, int):
@ -291,7 +278,7 @@ class Layout:
q: List[int] = cast(List[int], neighbors.get(which, [])) q: List[int] = cast(List[int], neighbors.get(which, []))
if not q: if not q:
return False 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: 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: if overlay_for is not None and overlay_for in all_windows:
@ -349,7 +336,7 @@ class Layout:
def xlayout( def xlayout(
self, self,
all_windows: WindowList, groups: Iterator[WindowGroup],
bias: Optional[Sequence[float]] = None, bias: Optional[Sequence[float]] = None,
start: Optional[int] = None, start: Optional[int] = None,
size: Optional[int] = None, size: Optional[int] = None,
@ -357,7 +344,7 @@ class Layout:
) -> LayoutDimension: ) -> LayoutDimension:
decoration_pairs = tuple( decoration_pairs = tuple(
(g.decoration('left'), g.decoration('right')) for i, g in (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: if start is None:
start = lgd.central.left start = lgd.central.left
@ -367,7 +354,7 @@ class Layout:
def ylayout( def ylayout(
self, self,
all_windows: WindowList, groups: Iterator[WindowGroup],
bias: Optional[Sequence[float]] = None, bias: Optional[Sequence[float]] = None,
start: Optional[int] = None, start: Optional[int] = None,
size: Optional[int] = None, size: Optional[int] = None,
@ -375,7 +362,7 @@ class Layout:
) -> LayoutDimension: ) -> LayoutDimension:
decoration_pairs = tuple( decoration_pairs = tuple(
(g.decoration('top'), g.decoration('bottom')) for i, g in (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: if start is None:
start = lgd.central.top start = lgd.central.top
@ -383,37 +370,35 @@ class Layout:
size = lgd.central.height size = lgd.central.height
return layout_dimension(start, size, lgd.cell_height, decoration_pairs, bias=bias, left_align=lgd.align_top_left) 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: def set_window_group_geometry(self, wg: WindowGroup, xl: LayoutData, yl: LayoutData) -> WindowGeometry:
wg = window_geometry_from_layouts(xl, yl) geom = window_geometry_from_layouts(xl, yl)
w.set_geometry(wg) wg.set_geometry(geom)
self.blank_rects.extend(blank_rects_for_window(wg)) self.blank_rects.extend(blank_rects_for_window(geom))
return geom
def do_layout(self, windows: WindowList) -> None: def do_layout(self, windows: WindowList) -> None:
raise NotImplementedError() 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': []} return {'left': [], 'right': [], 'top': [], 'bottom': []}
def compute_needs_borders_map(self, windows: WindowList, active_window: Optional[WindowType]) -> Dict[int, bool]: def compute_needs_borders_map(self, all_windows: WindowList) -> Dict[int, bool]:
return {w.id: ((w is active_window and lgd.draw_active_borders) or w.needs_attention) for w in windows} 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: if lgd.draw_minimal_borders:
needs_borders_map = self.compute_needs_borders_map(windows, active_window) needs_borders_map = self.compute_needs_borders_map(windows)
yield from self.minimal_borders(windows, active_window, needs_borders_map) yield from self.minimal_borders(windows, needs_borders_map)
else: 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]: def window_independent_borders(self, windows: WindowList, active_window: Optional[WindowType] = None) -> Generator[Edges, None, None]:
return return
yield Edges() # type: ignore yield Edges() # type: ignore
def minimal_borders(self, windows: WindowList, active_window: Optional[WindowType], needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]: def minimal_borders(self, windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
for w in windows: for needs_border in needs_borders_map.values():
if w is not active_window or lgd.draw_active_borders or w.needs_attention: yield all_borders if needs_border else no_borders
yield all_borders
else:
yield no_borders
def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]: def layout_action(self, action_name: str, args: Sequence[str], all_windows: WindowList) -> Optional[bool]:
pass pass

View File

@ -11,7 +11,7 @@ from kitty.typing import WindowType
from kitty.window_list import WindowList from kitty.window_list import WindowList
from .base import ( from .base import (
Borders, InternalNeighborsMap, Layout, LayoutData, LayoutDimension, Borders, NeighborsMap, Layout, LayoutData, LayoutDimension,
ListOfWindows, all_borders, layout_dimension, lgd, no_borders, ListOfWindows, all_borders, layout_dimension, lgd, no_borders,
variable_bias variable_bias
) )
@ -213,7 +213,7 @@ class Grid(Layout):
for border in rows[:-1]: for border in rows[:-1]:
yield border 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) n = len(windows)
if n < 4: if n < 4:
return neighbors_for_tall_window(1, window, windows) return neighbors_for_tall_window(1, window, windows)

View File

@ -11,7 +11,7 @@ from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowList from kitty.window_list import WindowList
from .base import ( 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 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 parent.modify_size_of_child(which, increment, is_horizontal, layout_object)
return False 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]: def quadrant(is_horizontal: bool, is_first: bool) -> Tuple[EdgeLiteral, EdgeLiteral]:
if is_horizontal: if is_horizontal:
@ -407,10 +407,10 @@ class Splits(Layout):
if pair.between_border is not None: if pair.between_border is not None:
yield pair.between_border 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 window_id = window.overlay_for or window.id
pair = self.pairs_root.pair_for_window(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: if pair is not None:
pair.neighbors_for_window(window_id, ans, self) pair.neighbors_for_window(window_id, ans, self)
return ans return ans

View File

@ -3,30 +3,32 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net> # License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from itertools import repeat 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.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowList from kitty.window_list import WindowList
from .base import ( from .base import (
Borders, InternalNeighborsMap, Layout, LayoutDimension, LayoutOpts, Borders, Layout, LayoutDimension, LayoutOpts, NeighborsMap, all_borders,
ListOfWindows, all_borders, lgd, no_borders, normalize_biases, lgd, no_borders, normalize_biases, safe_increment_bias, variable_bias
safe_increment_bias, variable_bias
) )
def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, all_windows: WindowList) -> NeighborsMap:
idx = windows.index(window) wg = all_windows.group_for_window(window)
prev = None if idx == 0 else windows[idx-1] assert wg is not None
nxt = None if idx == len(windows) - 1 else windows[idx+1] groups = tuple(all_windows.iter_all_layoutable_groups())
ans: InternalNeighborsMap = {'left': [prev.id] if prev is not None else [], 'right': [], 'top': [], 'bottom': []} 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 idx < num_full_size_windows - 1:
if nxt is not None: if nxt is not None:
ans['right'] = [nxt.id] ans['right'] = [nxt.id]
elif idx == num_full_size_windows - 1: 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: 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: if idx > num_full_size_windows and prev is not None:
ans['top'] = [prev.id] ans['top'] = [prev.id]
if nxt is not None: if nxt is not None:
@ -72,13 +74,13 @@ class Tall(Layout):
self.biased_map: Dict[int, float] = {} self.biased_map: Dict[int, float] = {}
return True return True
def variable_layout(self, windows: ListOfWindows, biased_map: Dict[int, float]) -> LayoutDimension: def variable_layout(self, all_windows: WindowList, biased_map: Dict[int, float]) -> LayoutDimension:
windows = windows[self.num_full_size_windows:] num = all_windows.num_groups - self.num_full_size_windows
bias = variable_bias(len(windows), biased_map) if len(windows) > 1 else None bias = variable_bias(num, biased_map) if num > 1 else None
return self.perp_axis_layout(windows, bias=bias) 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: def apply_bias(self, idx: int, increment: float, all_windows: WindowList, is_horizontal: bool = True) -> bool:
num_windows = len(top_level_windows) num_windows = all_windows.num_groups
if self.main_is_horizontal == is_horizontal: if self.main_is_horizontal == is_horizontal:
before_main_bias = self.main_bias before_main_bias = self.main_bias
ncols = self.num_full_size_windows + 1 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: if idx < self.num_full_size_windows or num_of_short_windows < 2:
return False return False
idx -= self.num_full_size_windows 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.) before = self.biased_map.get(idx, 0.)
candidate = self.biased_map.copy() candidate = self.biased_map.copy()
candidate[idx] = after = before + increment 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 return False
self.biased_map = candidate self.biased_map = candidate
return before != after return before != after
def do_layout(self, windows: WindowList, active_window_idx: int) -> None: def do_layout(self, all_windows: WindowList) -> None:
if len(windows) == 1: num = all_windows.num_groups
self.layout_single_window(windows[0]) if num == 1:
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return return
is_fat = not self.main_is_horizontal is_fat = not self.main_is_horizontal
if len(windows) <= self.num_full_size_windows + 1: if num <= self.num_full_size_windows + 1:
xlayout = self.main_axis_layout(windows, bias=self.main_bias) xlayout = self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=self.main_bias)
for i, (w, xl) in enumerate(zip(windows, xlayout)): for i, (wg, xl) in enumerate(zip(all_windows.iter_all_layoutable_groups(), xlayout)):
yl = next(self.perp_axis_layout([w])) yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat: if is_fat:
xl, yl = yl, xl xl, yl = yl, xl
self.set_window_geometry(w, i, xl, yl) self.set_window_group_geometry(wg, xl, yl)
return 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' attr: EdgeLiteral = 'bottom' if is_fat else 'right'
start = lgd.central.top if is_fat else lgd.central.left 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: if i >= self.num_full_size_windows:
break break
xl = next(xlayout) xl = next(xlayout)
yl = next(self.perp_axis_layout([w])) yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat: if is_fat:
xl, yl = yl, xl xl, yl = yl, xl
self.set_window_geometry(w, i, xl, yl) geom = self.set_window_group_geometry(wg, xl, yl)
start = getattr(w.geometry, attr) + w.effective_border() + w.effective_margin(attr) + w.effective_padding(attr) start = getattr(geom, attr) + wg.decoration(attr)
ylayout = self.variable_layout(windows, self.biased_map) ylayout = self.variable_layout(all_windows, self.biased_map)
size = (lgd.central.height if is_fat else lgd.central.width) - start 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: if i < self.num_full_size_windows:
continue continue
yl = next(ylayout) 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: if is_fat:
xl, yl = yl, xl 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) 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]: def minimal_borders(self, all_windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
last_i = len(windows) - 1 num = all_windows.num_groups
for i, w in enumerate(windows): last_i = num - 1
if needs_borders_map[w.id]: groups = tuple(all_windows.iter_all_layoutable_groups())
for i, wg in enumerate(groups):
if needs_borders_map[wg.id]:
yield all_borders yield all_borders
continue continue
if i < self.num_full_size_windows: 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 yield no_borders
else: else:
yield no_borders if i == last_i else self.only_main_border yield no_borders if i == last_i else self.only_main_border
@ -157,7 +163,7 @@ class Tall(Layout):
if i == last_i: if i == last_i:
yield no_borders yield no_borders
break break
if needs_borders_map[windows[i+1].id]: if needs_borders_map[groups[i+1].id]:
yield no_borders yield no_borders
else: else:
yield self.only_between_border yield self.only_between_border
@ -172,18 +178,21 @@ class Fat(Tall):
main_axis_layout = Layout.ylayout main_axis_layout = Layout.ylayout
perp_axis_layout = Layout.xlayout perp_axis_layout = Layout.xlayout
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> InternalNeighborsMap: def neighbors_for_window(self, window: WindowType, all_windows: WindowList) -> NeighborsMap:
idx = windows.index(window) wg = all_windows.group_for_window(window)
prev = None if idx == 0 else windows[idx-1] assert wg is not None
nxt = None if idx == len(windows) - 1 else windows[idx+1] groups = tuple(all_windows.iter_all_layoutable_groups())
ans: InternalNeighborsMap = {'left': [], 'right': [], 'top': [] if prev is None else [prev.id], 'bottom': []} 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 idx < self.num_full_size_windows - 1:
if nxt is not None: if nxt is not None:
ans['bottom'] = [nxt.id] ans['bottom'] = [nxt.id]
elif idx == self.num_full_size_windows - 1: 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: 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: if idx > self.num_full_size_windows and prev is not None:
ans['left'] = [prev.id] ans['left'] = [prev.id]
if nxt is not None: if nxt is not None:

View File

@ -8,7 +8,7 @@ from kitty.typing import WindowType
from kitty.window_list import WindowList from kitty.window_list import WindowList
from .base import ( from .base import (
Borders, InternalNeighborsMap, Layout, LayoutDimension, ListOfWindows, Borders, NeighborsMap, Layout, LayoutDimension, ListOfWindows,
all_borders, no_borders, variable_bias all_borders, no_borders, variable_bias
) )
@ -72,7 +72,7 @@ class Vertical(Layout):
else: else:
yield self.only_between_border 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) idx = windows.index(window)
before = [] if window is windows[0] else [windows[idx-1].id] before = [] if window is windows[0] else [windows[idx-1].id]
after = [] if window is windows[-1] else [windows[idx+1].id] after = [] if window is windows[-1] else [windows[idx+1].id]

View File

@ -188,11 +188,6 @@ class Tab: # {{{
def on_bell(self, window: Window) -> None: def on_bell(self, window: Window) -> None:
self.mark_tab_bar_dirty() 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: def relayout(self) -> None:
if self.windows: if self.windows:
self.current_layout(self.windows) self.current_layout(self.windows)
@ -201,13 +196,12 @@ class Tab: # {{{
def relayout_borders(self) -> None: def relayout_borders(self) -> None:
tm = self.tab_manager_ref() tm = self.tab_manager_ref()
if tm is not None: if tm is not None:
visible_windows = list(self.visible_windows())
w = self.active_window w = self.active_window
ly = self.current_layout ly = self.current_layout
self.borders( self.borders(
windows=visible_windows, active_window=w, all_windows=self.windows,
current_layout=ly, extra_blank_rects=tm.blank_rects, 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: if w is not None:
w.change_titlebar_color() w.change_titlebar_color()
@ -420,9 +414,9 @@ class Tab: # {{{
neighbors = self.current_layout.neighbors(self.windows) neighbors = self.current_layout.neighbors(self.windows)
candidates = cast(Optional[Tuple[int, ...]], neighbors.get(which)) candidates = cast(Optional[Tuple[int, ...]], neighbors.get(which))
if candidates: 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): if self.current_layout.move_window(self.windows, delta):
self.relayout() self.relayout()

View File

@ -44,6 +44,13 @@ class WindowGroup:
return True return True
return False return False
@property
def needs_attention(self) -> bool:
for w in self.windows:
if w.needs_attention:
return True
return False
@property @property
def base_window_id(self) -> int: def base_window_id(self) -> int:
return self.windows[0].id if self.windows else 0 return self.windows[0].id if self.windows else 0
@ -71,10 +78,39 @@ class WindowGroup:
w = self.windows[0] w = self.windows[0]
return w.effective_margin(which, is_single_window=is_single_window) + w.effective_border() * border_mult + w.effective_padding(which) 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: def set_geometry(self, geom: WindowGeometry) -> None:
for w in self.windows: for w in self.windows:
w.set_geometry(geom) 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: class WindowList:
@ -149,6 +185,11 @@ class WindowList:
changed = True changed = True
return changed 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: def change_tab(self, tab: TabType) -> None:
self.tabref = weakref.ref(tab) self.tabref = weakref.ref(tab)
@ -158,8 +199,8 @@ class WindowList:
for window in g: for window in g:
yield window, window.id == aw yield window, window.id == aw
def iter_all_layoutable_groups(self) -> Iterator[WindowGroup]: def iter_all_layoutable_groups(self, only_visible: bool = False) -> Iterator[WindowGroup]:
return iter(self.groups) 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: def make_previous_group_active(self, which: int = 1, notify: bool = False) -> None:
which = max(1, which) which = max(1, which)
@ -186,6 +227,12 @@ class WindowList:
if q in g: if q in g:
return 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]: def windows_in_group_of(self, x: WindowOrId) -> Iterator[WindowType]:
g = self.group_for_window(x) g = self.group_for_window(x)
if g is not None: if g is not None:
@ -289,16 +336,15 @@ class WindowList:
def activate_next_window_group(self, delta: int) -> None: def activate_next_window_group(self, delta: int) -> None:
self.set_active_group_idx(wrap_increment(self.active_group_idx, self.num_groups, delta)) 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: if self.active_group_idx < 0 or not self.groups:
return False return False
target = -1 target = -1
if by is not None: if by is not None:
target = wrap_increment(self.active_group_idx, self.num_groups, by) target = wrap_increment(self.active_group_idx, self.num_groups, by)
if to_group_with_window_id is not None: if to_group is not None:
q = self.id_map[to_group_with_window_id]
for i, group in enumerate(self.groups): for i, group in enumerate(self.groups):
if q in group: if group.id == to_group:
target = i target = i
break break
if target > -1: if target > -1:
@ -308,3 +354,15 @@ class WindowList:
self.set_active_group_idx(target) self.set_active_group_idx(target)
return True return True
return False 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