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 - 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] 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. 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`` There are options to control how the screen is split horizontally ``bias``
(an integer between ``10`` and ``90``) and options to control how many (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:: 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. 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`` There are options to control how the screen is split vertically ``bias``
(an integer between ``10`` and ``90``) and options to control how many (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
for the options is shown below:: ``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 # vim:fileencoding=utf-8
# 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 islice, repeat
from typing import Dict, Generator, List, Tuple from typing import Dict, Generator, List, Tuple
from kitty.conf.utils import to_bool
from kitty.typing import EdgeLiteral, WindowType from kitty.typing import EdgeLiteral, WindowType
from kitty.window_list import WindowList 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) wg = all_windows.group_for_window(window)
assert wg is not None assert wg is not None
groups = tuple(all_windows.iter_all_layoutable_groups()) groups = tuple(all_windows.iter_all_layoutable_groups())
idx = groups.index(wg) idx = groups.index(wg)
prev = None if idx == 0 else groups[idx-1] prev = None if idx == 0 else groups[idx-1]
nxt = None if idx == len(groups) - 1 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 idx < num_full_size_windows - 1:
if nxt is not None: if nxt is not None:
ans['right'] = [nxt.id] ans[main_after] = [nxt.id]
elif idx == num_full_size_windows - 1: 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: 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: 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: if nxt is not None:
ans['bottom'] = [nxt.id] ans[cross_after] = [nxt.id]
return ans return ans
class TallLayoutOpts(LayoutOpts): class TallLayoutOpts(LayoutOpts):
bias: Tuple[float, ...] = () bias: Tuple[float, ...] = ()
full_size: int = 1 full_size: int = 1
mirrored: bool = False
def __init__(self, data: Dict[str, str]): def __init__(self, data: Dict[str, str]):
try: try:
self.full_size = int(data.get('full_size', 1)) self.full_size = int(data.get('full_size', 1))
except Exception: except Exception:
@ -53,6 +68,7 @@ class TallLayoutOpts(LayoutOpts):
b = 0.5 b = 0.5
b = max(0.1, min(b, 0.9)) b = max(0.1, min(b, 0.9))
self.bias = tuple(repeat(b / fs, fs)) + (1.0 - b,) self.bias = tuple(repeat(b / fs, fs)) + (1.0 - b,)
self.mirrored = to_bool(data.get('mirrored', 'false'))
class Tall(Layout): class Tall(Layout):
@ -61,6 +77,7 @@ class Tall(Layout):
main_is_horizontal = True main_is_horizontal = True
only_between_border = Borders(False, False, False, True) only_between_border = Borders(False, False, False, True)
only_main_border = Borders(False, False, True, False) only_main_border = Borders(False, False, True, False)
only_main_border_mirrored = Borders(True, False, False, False)
layout_opts = TallLayoutOpts({}) layout_opts = TallLayoutOpts({})
main_axis_layout = Layout.xlayout main_axis_layout = Layout.xlayout
perp_axis_layout = Layout.ylayout perp_axis_layout = Layout.ylayout
@ -110,20 +127,38 @@ class Tall(Layout):
self.layout_single_window_group(next(all_windows.iter_all_layoutable_groups())) 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
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: if num <= self.num_full_size_windows + 1:
xlayout = self.main_axis_layout(all_windows.iter_all_layoutable_groups(), bias=self.main_bias) if mirrored:
for i, (wg, xl) in enumerate(zip(all_windows.iter_all_layoutable_groups(), xlayout)): 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,)))) 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_group_geometry(wg, xl, yl) self.set_window_group_geometry(wg, xl, yl)
return 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 start = lgd.central.top if is_fat else lgd.central.left
for i, wg in enumerate(all_windows.iter_all_layoutable_groups()): 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: if i >= self.num_full_size_windows:
break break
xl = next(xlayout) xl = next(xlayout)
@ -132,8 +167,9 @@ class Tall(Layout):
xl, yl = yl, xl xl, yl = yl, xl
geom = self.set_window_group_geometry(wg, xl, yl) geom = self.set_window_group_geometry(wg, xl, yl)
start = getattr(geom, attr) + wg.decoration(attr) 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 size = (lgd.central.height if is_fat else lgd.central.width) - start
ylayout = self.variable_layout(all_windows, self.biased_map)
for i, wg in enumerate(all_windows.iter_all_layoutable_groups()): 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
@ -144,9 +180,11 @@ class Tall(Layout):
self.set_window_group_geometry(wg, xl, yl) self.set_window_group_geometry(wg, xl, yl)
def neighbors_for_window(self, window: WindowType, windows: WindowList) -> NeighborsMap: 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]: 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 num = all_windows.num_groups
last_i = num - 1 last_i = num - 1
groups = tuple(all_windows.iter_all_layoutable_groups()) groups = tuple(all_windows.iter_all_layoutable_groups())
@ -155,10 +193,11 @@ class Tall(Layout):
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[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 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 only_main_border
continue continue
if i == last_i: if i == last_i:
yield no_borders yield no_borders
@ -175,26 +214,6 @@ class Fat(Tall):
main_is_horizontal = False main_is_horizontal = False
only_between_border = Borders(False, False, True, False) only_between_border = Borders(False, False, True, False)
only_main_border = Borders(False, False, False, True) only_main_border = Borders(False, False, False, True)
only_main_border_mirrored = Borders(False, True, False, False)
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, 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