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 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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -3,30 +3,32 @@
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
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:

View File

@ -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]

View File

@ -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()

View File

@ -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