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 - 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`) 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 - Allow choosing OpenType features for individual fonts via the
:opt:`font_features` option. :opt:`font_features` option.

View File

@ -225,8 +225,8 @@ using the :sc:`new_window` key combination.
Currently, there are five layouts available, Currently, there are five layouts available,
* **Stack** -- Only a single maximized window is shown at a time * **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 * **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 window is shown full width on the top, the rest of the windows are shown side-by-side on the bottom * **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 * **Grid** -- All windows are shown in a grid
* **Horizontal** -- All windows are shown side-by-side * **Horizontal** -- All windows are shown side-by-side
* **Vertical** -- All windows are shown one below the other * **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`` Some layouts take options to control their behavior. For example, the ``fat``
and ``tall`` layouts accept the ``bias`` option to control how the available and ``tall`` layouts accept the ``bias`` and ``full_size`` options to control
space is split up. To specify the option, in :opt:`kitty.conf <enabled_layouts>` use:: 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 This will have ``2`` instead of a single tall window, that occupy ``70%``
any number between 10 and 90. 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 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 is some layout you want, take a look at `layout.py

View File

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