Allow having more than one full height window in the :code:tall layout

Fixes #2276
This commit is contained in:
Kovid Goyal 2020-01-15 11:28:58 +05:30
parent e8121b39ec
commit cf8d665eb7
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
3 changed files with 125 additions and 59 deletions

View File

@ -13,6 +13,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
the selected file at the selected line in vim or a configurable editor (:iss:`2268`)
- Allow having more than one full height window in the :code:`tall` layout
(:iss:`2276`)
- Allow choosing OpenType features for individual fonts via the
:opt:`font_features` option.

View File

@ -225,8 +225,8 @@ using the :sc:`new_window` key combination.
Currently, there are five layouts available,
* **Stack** -- Only a single maximized window is shown at a time
* **Tall** -- One window is shown full height on the left, the rest of the windows are shown one below the other on the right
* **Fat** -- One window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom
* **Tall** -- One (or optionally more) windows are shown full height on the left, the rest of the windows are shown one below the other on the right
* **Fat** -- One (or optionally more) windows are shown full width on the top, the rest of the windows are shown side-by-side on the bottom
* **Grid** -- All windows are shown in a grid
* **Horizontal** -- All windows are shown side-by-side
* **Vertical** -- All windows are shown one below the other
@ -256,13 +256,15 @@ the resizing increment (a positive integer that defaults to 1).
Some layouts take options to control their behavior. For example, the ``fat``
and ``tall`` layouts accept the ``bias`` option to control how the available
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
how the available space is split up.
To specify the option, in :opt:`kitty.conf <enabled_layouts>` use::
enabled_layouts tall:bias=70
enabled_layouts tall:bias=70;full_size=2
This will make the tall window occupy ``70%`` of available width. ``bias`` can be
any number between 10 and 90.
This will have ``2`` instead of a single tall window, that occupy ``70%``
instead of ``50%`` of available width. ``bias`` can be any number between 10
and 90.
Writing a new layout only requires about a hundred lines of code, so if there
is some layout you want, take a look at `layout.py

View File

@ -395,8 +395,11 @@ class Layout: # {{{
br = self.blank_rects
left_blank_rect(first_window, br), top_blank_rect(first_window, br), right_blank_rect(last_window, br)
def between_blank_rect(self, left_window, right_window):
def between_blank_rect(self, left_window, right_window, vertical=True):
if vertical:
self.blank_rects.append(Rect(left_window.geometry.right, central.top, right_window.geometry.left, central.bottom + 1))
else:
self.blank_rects.append(Rect(central.left, left_window.geometry.top, central.right + 1, right_window.geometry.bottom))
def bottom_blank_rect(self, window):
self.blank_rects.append(Rect(window.geometry.left, window.geometry.bottom, window.geometry.right, central.bottom + 1))
@ -449,28 +452,34 @@ class Tall(Layout): # {{{
only_between_border = False, False, False, True
only_main_border = False, False, True, False
@property
def num_full_size_windows(self):
return self.layout_opts['full_size']
def remove_all_biases(self):
self.main_bias = list(self.layout_opts['bias'])
self.biased_map = {}
return True
def variable_layout(self, num_windows, biased_map):
num_windows -= 1
num_windows -= self.num_full_size_windows
return self.vlayout(num_windows, bias=variable_bias(num_windows, biased_map) if num_windows > 1 else None)
def apply_bias(self, idx, increment, num_windows, is_horizontal):
if self.main_is_horizontal == is_horizontal:
before = self.main_bias
if idx == 0:
self.main_bias = [safe_increment_bias(self.main_bias[0], increment), safe_increment_bias(self.main_bias[1], -increment)]
else:
self.main_bias = [safe_increment_bias(self.main_bias[0], -increment), safe_increment_bias(self.main_bias[1], increment)]
ncols = self.num_full_size_windows + 1
biased_col = idx if idx < self.num_full_size_windows else (ncols - 1)
self.main_bias = [
safe_increment_bias(self.main_bias[i], increment * (1 if i == biased_col else -1)) for i in range(ncols)
]
self.main_bias = normalize_biases(self.main_bias)
after = self.main_bias
else:
if idx == 0 or num_windows < 3:
num_of_short_windows = num_windows - self.num_full_size_windows
if idx < self.num_full_size_windows or num_of_short_windows < 2:
return False
idx -= 1
idx -= self.num_full_size_windows
before_layout = list(self.variable_layout(num_windows, self.biased_map))
candidate = self.biased_map.copy()
before = candidate.get(idx, 0)
@ -484,40 +493,70 @@ class Tall(Layout): # {{{
def parse_layout_opts(self, layout_opts):
ans = Layout.parse_layout_opts(self, layout_opts)
try:
ans['bias'] = int(ans.get('bias', 50)) / 100
ans['full_size'] = int(ans.get('full_size', 1))
except Exception:
ans['bias'] = 0.5
ans['bias'] = max(0.1, min(ans['bias'], 0.9))
ans['bias'] = ans['bias'], 1.0 - ans['bias']
ans['full_size'] = 1
ans['full_size'] = fs = max(1, min(ans['full_size'], 100))
try:
b = int(ans.get('bias', 50)) / 100
except Exception:
b = 0.5
b = max(0.1, min(b, 0.9))
ans['bias'] = tuple(repeat(b / fs, fs)) + (1.0 - b,)
return ans
def do_layout(self, windows, active_window_idx):
if len(windows) == 1:
return self.layout_single_window(windows[0])
xlayout = self.xlayout(2, bias=self.main_bias)
xstart, xnum = next(xlayout)
ystart, ynum = next(self.vlayout(1))
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
xstart, xnum = next(xlayout)
ylayout = self.variable_layout(len(windows), self.biased_map)
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, 1, None), ylayout)):
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
# right bottom blank rect
self.bottom_blank_rect(windows[i + 1])
y, ynum = next(self.vlayout(1))
if len(windows) <= self.num_full_size_windows:
bias = normalize_biases(self.main_bias[:-1])
xlayout = self.xlayout(self.num_full_size_windows, bias=bias)
for i, (w, (x, xnum)) in enumerate(zip(windows, xlayout)):
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
if i > 0:
self.between_blank_rect(windows[i-1], windows[i])
# bottom blank rect
self.bottom_blank_rect(windows[i])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# between blank rect
self.between_blank_rect(windows[0], windows[1])
# left bottom blank rect
self.bottom_blank_rect(windows[0])
return
xlayout = self.xlayout(self.num_full_size_windows + 1, bias=self.main_bias)
for i in range(self.num_full_size_windows):
w = windows[i]
x, xnum = next(xlayout)
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
self.between_blank_rect(windows[i], windows[i+1])
# bottom blank rect
self.bottom_blank_rect(windows[i])
x, xnum = next(xlayout)
ylayout = self.variable_layout(len(windows), self.biased_map)
for i, (w, (ystart, ynum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), ylayout)):
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, ystart, ynum))
# right bottom blank rect
self.bottom_blank_rect(windows[-1])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'right': windows[1:], 'top': [], 'bottom': []}
idx = windows.index(window)
return {'left': [windows[0]], 'right': [], 'top': [] if idx <= 1 else [windows[idx-1]],
'bottom': [] if window is windows[-1] else [windows[idx+1]]}
prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1]
ans = {'left': [prev] if prev is not None else [], 'right': [], 'top': [], 'bottom': []}
if idx < self.num_full_size_windows - 1:
if nxt is not None:
ans['right'] = [nxt]
elif idx == self.num_full_size_windows - 1:
ans['right'] = windows[idx+1:]
else:
ans['left'] = [windows[self.num_full_size_windows - 1]]
if idx > self.num_full_size_windows:
ans['top'] = [prev]
if nxt is not None:
ans['bottom'] = [nxt]
return ans
def minimal_borders(self, windows, active_window, needs_borders_map):
last_i = len(windows) - 1
@ -525,11 +564,11 @@ class Tall(Layout): # {{{
if needs_borders_map[w.id]:
yield all_borders
continue
if i == 0:
if last_i == 1 and needs_borders_map[windows[1].id]:
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]:
yield no_borders
else:
yield self.only_main_border
yield no_borders if i == last_i else self.only_main_border
continue
if i == last_i:
yield no_borders
@ -552,31 +591,53 @@ class Fat(Tall): # {{{
def do_layout(self, windows, active_window_idx):
if len(windows) == 1:
return self.layout_single_window(windows[0])
xstart, xnum = next(self.xlayout(1))
ylayout = self.ylayout(2, bias=self.main_bias)
ystart, ynum = next(ylayout)
windows[0].set_geometry(0, window_geometry(xstart, xnum, ystart, ynum))
xlayout = self.variable_layout(len(windows), self.biased_map)
ystart, ynum = next(ylayout)
for i, (w, (xstart, xnum)) in enumerate(zip(islice(windows, 1, None), xlayout)):
w.set_geometry(i + 1, window_geometry(xstart, xnum, ystart, ynum))
x, xnum = next(self.vlayout(1))
if len(windows) <= self.num_full_size_windows:
bias = normalize_biases(self.main_bias[:-1])
ylayout = self.ylayout(self.num_full_size_windows, bias=bias)
for i, (w, (y, ynum)) in enumerate(zip(windows, ylayout)):
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
if i > 0:
# bottom between blank rect
self.between_blank_rect(windows[i - 1], windows[i])
self.between_blank_rect(windows[i-1], windows[i], vertical=False)
# bottom blank rect
self.bottom_blank_rect(windows[-1])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
return
ylayout = self.ylayout(self.num_full_size_windows + 1, bias=self.main_bias)
for i in range(self.num_full_size_windows):
w = windows[i]
y, ynum = next(ylayout)
w.set_geometry(i, window_geometry(x, xnum, y, ynum))
self.between_blank_rect(windows[i], windows[i+1], vertical=False)
y, ynum = next(ylayout)
xlayout = self.variable_layout(len(windows), self.biased_map)
for i, (w, (x, xnum)) in enumerate(zip(islice(windows, self.num_full_size_windows, None), xlayout)):
w.set_geometry(i + self.num_full_size_windows, window_geometry(x, xnum, y, ynum))
# bottom blank rect
self.bottom_blank_rect(windows[i])
# left, top and right blank rects
self.simple_blank_rects(windows[0], windows[-1])
# top bottom blank rect
self.bottom_blank_rect(windows[0])
# bottom blank rect
self.blank_rects.append(Rect(windows[0].geometry.left, windows[0].geometry.bottom, windows[-1].geometry.right, central.bottom + 1))
def neighbors_for_window(self, window, windows):
if window is windows[0]:
return {'left': [], 'bottom': windows[1:], 'top': [], 'right': []}
idx = windows.index(window)
return {'top': [windows[0]], 'bottom': [], 'left': [] if idx <= 1 else [windows[idx-1]],
'right': [] if window is windows[-1] else [windows[idx+1]]}
prev = None if idx == 0 else windows[idx-1]
nxt = None if idx == len(windows) - 1 else windows[idx+1]
ans = {'left': [], 'right': [], 'top': [] if prev is None else [prev], 'bottom': []}
if idx < self.num_full_size_windows - 1:
if nxt is not None:
ans['bottom'] = [nxt]
elif idx == self.num_full_size_windows - 1:
ans['bottom'] = windows[idx+1:]
else:
ans['top'] = [windows[self.num_full_size_windows - 1]]
if idx > self.num_full_size_windows:
ans['left'] = [prev]
if nxt is not None:
ans['right'] = [nxt]
return ans
# }}}