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:
parent
e1a0a195a9
commit
25623c758d
@ -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]
|
||||||
--------------------
|
--------------------
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
┌──────────────────────────────┐
|
┌──────────────────────────────┐
|
||||||
│ │
|
│ │
|
||||||
|
|||||||
@ -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
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user