diff --git a/docs/changelog.rst b/docs/changelog.rst index 27a155f7f..bb0b2b134 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,9 @@ To update |kitty|, :doc:`follow the instructions `. - 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] -------------------- diff --git a/docs/layouts.rst b/docs/layouts.rst index dacd993e8..893e708cf 100644 --- a/docs/layouts.rst +++ b/docs/layouts.rst @@ -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 ┌──────────────────────────────┐ │ │ diff --git a/kitty/layout/tall.py b/kitty/layout/tall.py index 6dc3914b9..2b861b569 100644 --- a/kitty/layout/tall.py +++ b/kitty/layout/tall.py @@ -2,9 +2,10 @@ # vim:fileencoding=utf-8 # License: GPLv3 Copyright: 2020, Kovid Goyal -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