Tall and Fat layouts: Add a `mirrored` option to put the full size window on the opposite edge of the screen

Fixes #2654
This commit is contained in:
Kovid Goyal 2020-05-13 23:19:34 +05:30
parent e1a0a195a9
commit 25623c758d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 79 additions and 53 deletions

View File

@ -9,6 +9,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- Allow multiple overlay windows per normal window
- Tall and Fat layouts: Add a ``mirrored`` option to put the full size window
on the opposite edge of the screen (:iss:`2654`)
0.17.4 [2020-05-09]
--------------------

View File

@ -46,10 +46,12 @@ Displays one (or optionally more) full height windows on the left half of the
screen. Remaining windows are tiled vertically on the right half of the screen.
There are options to control how the screen is split horizontally ``bias``
(an integer between ``10`` and ``90``) and options to control how many
full-height windows there are ``full_size`` (a positive integer). The syntax
full-height windows there are ``full_size`` (a positive integer). The
``mirrored`` option when set to ``true`` will cause the short windows to be
on the left side of the screen instead of the right. The syntax
for the options is shown below::
enabled_layouts tall:bias=50;full_size=1
enabled_layouts tall:bias=50;full_size=1;mirrored=false
┌──────────────┬───────────────┐
│ │ │
@ -72,10 +74,12 @@ Displays one (or optionally more) full width windows on the top half of the
screen. Remaining windows are tiled horizontally on the bottom half of the screen.
There are options to control how the screen is split vertically ``bias``
(an integer between ``10`` and ``90``) and options to control how many
full-height windows there are ``full_size`` (a positive integer). The syntax
for the options is shown below::
full-height windows there are ``full_size`` (a positive integer). The
``mirrored`` option when set to ``true`` will cause the narrow windows to be
on the top of the screen instead of the bottom. The syntax for the options is
shown below::
enabled_layouts fat:bias=50;full_size=1
enabled_layouts fat:bias=50;full_size=1;mirrored=false
┌──────────────────────────────┐
│ │

View File

@ -2,9 +2,10 @@
# vim:fileencoding=utf-8
# License: GPLv3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from itertools import repeat
from itertools import islice, repeat
from typing import Dict, Generator, List, Tuple
from kitty.conf.utils import to_bool
from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowList
@ -14,34 +15,48 @@ from .base import (
)
def neighbors_for_tall_window(num_full_size_windows: int, window: WindowType, all_windows: WindowList) -> NeighborsMap:
def neighbors_for_tall_window(
num_full_size_windows: int,
window: WindowType,
all_windows: WindowList,
mirrored: bool = False,
main_is_horizontal: bool = True
) -> 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': []}
ans: NeighborsMap = {'left': [], 'right': [], 'top': [], 'bottom': []}
main_before: EdgeLiteral = 'left' if main_is_horizontal else 'top'
main_after: EdgeLiteral = 'right' if main_is_horizontal else 'bottom'
cross_before: EdgeLiteral = 'top' if main_is_horizontal else 'left'
cross_after: EdgeLiteral = 'bottom' if main_is_horizontal else 'right'
if mirrored:
main_before, main_after = main_after, main_before
if prev is not None:
ans[main_before] = [prev.id]
if idx < num_full_size_windows - 1:
if nxt is not None:
ans['right'] = [nxt.id]
ans[main_after] = [nxt.id]
elif idx == num_full_size_windows - 1:
ans['right'] = [w.id for w in groups[idx+1:]]
ans[main_after] = [w.id for w in groups[idx+1:]]
else:
ans['left'] = [groups[num_full_size_windows - 1].id]
ans[main_before] = [groups[num_full_size_windows - 1].id]
if idx > num_full_size_windows and prev is not None:
ans['top'] = [prev.id]
ans[cross_before] = [prev.id]
if nxt is not None:
ans['bottom'] = [nxt.id]
ans[cross_after] = [nxt.id]
return ans
class TallLayoutOpts(LayoutOpts):
bias: Tuple[float, ...] = ()
full_size: int = 1
mirrored: bool = False
def __init__(self, data: Dict[str, str]):
try:
self.full_size = int(data.get('full_size', 1))
except Exception:
@ -53,6 +68,7 @@ class TallLayoutOpts(LayoutOpts):
b = 0.5
b = max(0.1, min(b, 0.9))
self.bias = tuple(repeat(b / fs, fs)) + (1.0 - b,)
self.mirrored = to_bool(data.get('mirrored', 'false'))
class Tall(Layout):
@ -61,6 +77,7 @@ class Tall(Layout):
main_is_horizontal = True
only_between_border = Borders(False, False, False, True)
only_main_border = Borders(False, False, True, False)
only_main_border_mirrored = Borders(True, False, False, False)
layout_opts = TallLayoutOpts({})
main_axis_layout = Layout.xlayout
perp_axis_layout = Layout.ylayout
@ -110,30 +127,49 @@ class Tall(Layout):
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups()))
return
is_fat = not self.main_is_horizontal
mirrored = self.layout_opts.mirrored
groups = tuple(all_windows.iter_all_layoutable_groups())
main_bias = self.main_bias[::-1] if mirrored else self.main_bias
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)):
if mirrored:
groups = tuple(reversed(groups))
xlayout = self.main_axis_layout(iter(groups), bias=main_bias)
for wg, xl in zip(groups, xlayout):
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
return
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, 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(iter((wg,))))
if is_fat:
xl, yl = yl, xl
geom = self.set_window_group_geometry(wg, xl, yl)
start = getattr(geom, attr) + wg.decoration(attr)
size = 0
if mirrored:
fsg = groups[:self.num_full_size_windows + 1]
xlayout = self.main_axis_layout(reversed(fsg), bias=main_bias)
for i, wg in enumerate(reversed(fsg)):
xl = next(xlayout)
if i == 0:
size = xl.content_size + xl.space_before + xl.space_after
continue
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
self.set_window_group_geometry(wg, xl, yl)
else:
xlayout = self.main_axis_layout(islice(groups, self.num_full_size_windows + 1), bias=main_bias)
attr: EdgeLiteral = 'bottom' if is_fat else 'right'
for i, wg in enumerate(groups):
if i >= self.num_full_size_windows:
break
xl = next(xlayout)
yl = next(self.perp_axis_layout(iter((wg,))))
if is_fat:
xl, yl = yl, xl
geom = self.set_window_group_geometry(wg, xl, yl)
start = getattr(geom, attr) + wg.decoration(attr)
size = (lgd.central.height if is_fat else lgd.central.width) - start
ylayout = self.variable_layout(all_windows, self.biased_map)
size = (lgd.central.height if is_fat else lgd.central.width) - start
for i, wg in enumerate(all_windows.iter_all_layoutable_groups()):
if i < self.num_full_size_windows:
continue
@ -144,9 +180,11 @@ class Tall(Layout):
self.set_window_group_geometry(wg, xl, yl)
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, self.layout_opts.mirrored, self.main_is_horizontal)
def minimal_borders(self, all_windows: WindowList, needs_borders_map: Dict[int, bool]) -> Generator[Borders, None, None]:
mirrored = self.layout_opts.mirrored
only_main_border = self.only_main_border_mirrored if mirrored else self.only_main_border
num = all_windows.num_groups
last_i = num - 1
groups = tuple(all_windows.iter_all_layoutable_groups())
@ -155,10 +193,11 @@ class Tall(Layout):
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[groups[i+1].id]:
next_window_is_full_sized = last_i == i+1 or i+1 < self.num_full_size_windows
if next_window_is_full_sized and needs_borders_map[groups[i+1].id]:
yield no_borders
else:
yield no_borders if i == last_i else self.only_main_border
yield no_borders if i == last_i else only_main_border
continue
if i == last_i:
yield no_borders
@ -175,26 +214,6 @@ class Fat(Tall):
main_is_horizontal = False
only_between_border = Borders(False, False, True, False)
only_main_border = Borders(False, False, False, True)
only_main_border_mirrored = Borders(False, True, False, False)
main_axis_layout = Layout.ylayout
perp_axis_layout = Layout.xlayout
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 groups[idx+1:]]
else:
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:
ans['right'] = [nxt.id]
return ans